devops #claude-code #unc #permissions #auto-mode #coding-agents #change-control

A UNC Path Read Is Not a Local Allow

A quiet Read on a network share after you approved a local folder, or after auto mode, is not proof always-ask is off. Print UNC versus local versus the named Claude Code owner before you file the wrong ticket.

The junior approved Read on C:\work\app\config.yaml. The next turn reads \\fileserver\share\app\config.yaml with no dialog. Chat files the ticket: “always-ask is off. the sandbox already allowed it.”

I stop the run there. A UNC path read is not a local allow. Claude Code’s public notes that named this field are blunt: PreToolUse hook approvals and auto mode used to bypass the permission prompt for file reads from network (UNC) paths. Official permissions docs already say a command whose arguments include a network (UNC) path prompts, because looking that path up can send Windows credentials to the host it names. The human approved a local spelling. The second path is not that allow. [Source: https://github.com/anthropics/claude-code/releases/tag/v2.1.292] [Source: https://code.claude.com/docs/en/permissions]

I already refused to treat a leftover allowed-tools rule as you still in plan mode in A Leftover Allowed-Tools Rule Is Not You Still in Plan Mode, a repeated reply ID as a second approval in A Repeated Reply ID Is Not a Second Approval, and sandbox auto-allow as a retry tax on equals in Sandbox Auto-Allow Is Not a Retry Tax on Equals in Inline Scripts. This post is the same desk rule for a network-shaped read. Print the path class. Print whether the allow was local. Name who owns the Claude Code answers.

The question is not whether the prompt stayed quiet. The question is whether the named owner can still tell a UNC path read from a local allow.

Three columns: UNC path read, Local allow, Named Claude Code owner

The ticket that looks like always-ask-is-off

Juniors treat a quiet Read the way they treat a skipped always-ask. Yesterday they approved a file under the checkout. Today auto mode, or a PreToolUse hook that already said yes, reads the same filename through a share. They page the desk: “always-ask broke” or “the sandbox already allowed it.”

Two jobs collide on that row.

  1. Keep local allows local. Official permissions page: read-only file tools in the working directory and additional directories do not prompt. You extend that set with --add-dir, /add-dir, or additionalDirectories. You cannot add most network paths, such as the UNC share \\server\share, as working directories, because looking one up can contact the host it names. On Windows, map the share to a drive letter, then pass that drive at startup. [Source: https://code.claude.com/docs/en/permissions]
  2. Keep the network spelling on its own prompt. Official changelog, 6 October 2026: PreToolUse hook approvals and auto mode bypassing the permission prompt for file reads from network (UNC) paths is the named miss. After that date the miss is the field. It is not proof always-ask is off, and it is not proof a local allow covered the share. [Source: https://github.com/anthropics/claude-code/releases/tag/v2.1.292] [Source: https://code.claude.com/docs/en/changelog]

If you only screenshot “I already said yes to config.yaml,” you will file always-ask-broken. You will not file the UNC read.

I do not invent a fake overnight outage of every Windows share. I use the public contract. The network-shaped read is the ticket, not a version pin in the title.

What Claude Code actually named

Read the 6 October 2026 GitHub release body, then the permissions page, then the hooks page that names what a silent PreToolUse hook does. Do not trust a social recap. Do not trust this post without those pages.

The named field, published 6 October 2026:

Security: Fixed PreToolUse hook approvals and auto mode bypassing the permission prompt for file reads from network (UNC) paths

That sentence has three parts. Print all three on the ticket.

PartWhat it isWhat it is not
UNC / network path readA Read whose spelling names a share, a \\server\share host, or a /net/<host>/ automountProof a local folder allow still covers it
Local allowA yes on a path inside the working directory or an added local directoryA yes on every filename that happens to match
Named ownerThe human who answers Claude Code permission rowsA coding agent, a PreToolUse hook, or auto mode

Official permissions docs keep the network case loud. A command whose arguments include a network (UNC) path, such as \\server\share\file, prompts because accessing a network path can send your Windows credentials to the host it names. The same check applies to PowerShell tool commands. [Source: https://code.claude.com/docs/en/permissions]

Official hooks docs keep PreToolUse small. The hook fires before a tool call executes. It can block the call. Exit code 0 with no output means the hook has no decision to report, so the tool call continues through the normal permission flow. The hook can deny the call. Staying silent does not approve it. [Source: https://code.claude.com/docs/en/hooks]

The miss that looks like this ticket is the opposite mix: a PreToolUse yes, or auto mode, treated a network-shaped Read as already allowed. Right filename, wrong spelling. After 6 October 2026, a share read that skipped the prompt because a hook or auto mode had already waved a local path is that miss, not always-ask off. [Source: https://github.com/anthropics/claude-code/releases/tag/v2.1.292] [Source: https://code.claude.com/docs/en/hooks]

Where the pin sits after the decision, not in the title
Claude Code’s public GitHub release dates this field 6 October 2026 under the v2.1.292 heading (published_at 2026-10-06T18:59:30Z, not a prerelease). The same day’s notes also name leftover allowed-tools, first-run plugin install versus managed settings, and /ultrareview staged copies. npm dist-tags.latest on the morning this post shipped was 2.1.295; dist-tags.stable was 2.1.286. The field for this URL is the UNC line, not a pin in the title. Do not mix redirected rm, nested deny, leftover allowed-tools, or sandbox-equals into this heading. [Source: https://github.com/anthropics/claude-code/releases/tag/v2.1.292]

Four checks before you file

A mid-senior screenshots this list. A junior who only has the agent log still has a ticket they can close.

  1. Print the path spelling. Copy the Read argument as the tool received it. If the log only shows a basename, the ticket is “path missing,” not always-ask-broken.
  2. Print UNC versus local versus mapped drive. \\server\share\... or //server/share/... is UNC. /net/<host>/... is an automount. Z:\app\config.yaml after net use Z: is a mapped drive the permissions page tells you to pass with --add-dir. C:\work\app\config.yaml is local. Do not collapse those four into “the same file.” [Source: https://code.claude.com/docs/en/permissions]
  3. Print what was actually approved. A local working-directory Read, an --add-dir folder, a PreToolUse permissionDecision: "allow", or auto mode. Official hooks page: a silent hook is not an allow. [Source: https://code.claude.com/docs/en/hooks]
  4. Write one human name. CLAUDE_CODE_OWNER. That name answers “is this a UNC path read, or did we file always-ask-broken on a local allow.”

Four checks: Print the spelling, UNC versus local, What was approved, Named human

Probe the spelling before you file the ticket

Do not open the share “to see if the prompt still fires.” Classify the string. Save the next block as classify_path_shape.py. The probe never talks to Claude Code. It never mounts a drive. It never reads a file.

 1#!/usr/bin/env python3
 2"""Classify a Read path spelling. Never open the path. Never talk to a share."""
 3from __future__ import annotations
 4
 5from pathlib import Path
 6from typing import Any
 7
 8
 9def classify_spelling(raw: str) -> dict[str, Any]:
10    text = (raw or "").strip()
11    lowered = text.replace("/", "\\").lower()
12    is_unc = text.startswith("\\\\") or text.startswith("//")
13    is_automount = text.startswith("/net/")
14    is_drive = len(text) >= 3 and text[1] == ":" and text[0].isalpha()
15    is_local_posix = text.startswith("/") and not is_automount and not is_unc
16    if is_unc or is_automount:
17        ticket = "unc-path-read-not-a-local-allow"
18        reason = "network-shaped spelling — this field"
19    elif is_drive or is_local_posix:
20        ticket = "local-allow-or-other-field"
21        reason = "local spelling — not this UNC ticket"
22    else:
23        ticket = "owner-missing-or-path-missing"
24        reason = "cannot classify — print the raw Read argument"
25    return {
26        "raw": text,
27        "this_field": ticket == "unc-path-read-not-a-local-allow",
28        "ticket": ticket,
29        "reason": reason,
30        "looks_like_dav": "davwwwroot" in lowered or "@ssl@" in lowered,
31    }
32
33
34def classify_file(path: Path) -> dict[str, Any]:
35    line = path.read_text(encoding="utf-8").splitlines()[0] if path.exists() else ""
36    row = classify_spelling(line)
37    row["fixture"] = str(path)
38    return row
39
40
41if __name__ == "__main__":
42    import json
43    import sys
44
45    target = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("fixtures/read-path.txt")
46    print(json.dumps(classify_file(target), indent=2))

The fixture is one line. Put the Read argument in fixtures/read-path.txt. Do not put a live password, a live hostname you have not already redacted, or a production share URL you are not allowed to store.

1{
2  "owner": "Shinjae",
3  "approved_path": "C:\\work\\app\\config.yaml",
4  "read_path": "\\\\fileserver\\share\\app\\config.yaml",
5  "mode_when_read": "auto",
6  "pretooluse_decision": "allow",
7  "ticket": "unc-path-read-not-a-local-allow"
8}

Export CLAUDE_CODE_OWNER=Shinjae. Export APPROVED_PATH and READ_PATH from the log, not from memory. If the printer says ticket=unc-path-read-not-a-local-allow, close always-ask-broken. Open this field. If it says owner-missing-or-path-missing, the ticket is a missing name or a missing spelling, not a broken prompt.

 1#!/usr/bin/env python3
 2"""Print the UNC ticket. Never mount. Never call Claude Code."""
 3from __future__ import annotations
 4
 5import json
 6import os
 7from pathlib import Path
 8
 9
10def main() -> int:
11    owner = os.environ.get("CLAUDE_CODE_OWNER", "").strip()
12    approved = os.environ.get("APPROVED_PATH", "").strip()
13    read_path = os.environ.get("READ_PATH", "").strip()
14    mode = os.environ.get("PERMISSION_MODE_WHEN_READ", "").strip()
15    if not owner:
16        print("ticket=owner-missing")
17        return 2
18    from classify_path_shape import classify_spelling
19
20    read_row = classify_spelling(read_path)
21    approved_row = classify_spelling(approved)
22    this_field = read_row["this_field"] and not approved_row["this_field"]
23    ticket = (
24        "unc-path-read-not-a-local-allow"
25        if this_field
26        else read_row["ticket"]
27    )
28    payload = {
29        "owner": owner,
30        "approved_class": approved_row["ticket"],
31        "read_class": read_row["ticket"],
32        "mode_when_read": mode,
33        "ticket": ticket,
34    }
35    out = Path("unc-ticket.json")
36    out.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
37    print(f"ticket={ticket}")
38    print(f"owner={owner}")
39    return 0 if this_field else 1
40
41
42if __name__ == "__main__":
43    raise SystemExit(main())

If ticket=unc-path-read-not-a-local-allow, the later quiet Read is this post. If both spellings classify as local, this is not the field. If both classify as UNC, you still print the owner, then you still refuse to call that a local allow.

Ticket card: CLAUDE_CODE_OWNER, approved local path, network-shaped read, this ticket

Scan the log for the spelling, not for “quiet”

A quiet tool line is not a classifier. Scan for the Read argument. Redact the host if the log is leaving the building.

 1#!/usr/bin/env python3
 2"""Scan an agent log for Read paths. Never execute the Read."""
 3from __future__ import annotations
 4
 5import re
 6import sys
 7from pathlib import Path
 8
 9from classify_path_shape import classify_spelling
10
11READ_RE = re.compile(r"Read\(([^)]+)\)")
12
13
14def main() -> int:
15    path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("agent.log")
16    text = path.read_text(encoding="utf-8", errors="replace")
17    hits = 0
18    for i, line in enumerate(text.splitlines(), 1):
19        for match in READ_RE.finditer(line):
20            raw = match.group(1).strip().strip("\"'")
21            row = classify_spelling(raw)
22            print(f"{path}:{i}:this_field={row['this_field']} ticket={row['ticket']} {raw[:80]}")
23            hits += 1
24    print(f"hits={hits}")
25    return 0
26
27
28if __name__ == "__main__":
29    raise SystemExit(main())

If the scan prints this_field=True, the ticket is this post. If it only prints local spellings, look at leftover allowed-tools, redirected rm, or sandbox-equals — those are other URLs. If it prints nothing, file a missing owner, a missing log, or a different permission field.

Official docs still name the neighbor you must not collapse into this heading. You cannot add most UNC shares as working directories. Mapping the share to a drive letter, then passing that drive with --add-dir, is the documented Windows path. That mapped drive is still not a silent UNC spelling, and it is not a sandbox-bypass recipe. Print both. Split “we never added the drive” onto its own ticket. [Source: https://code.claude.com/docs/en/permissions]

Neighbor fields that are not this ticket

Keep the heading small. Neighbor fires from the same week are other URLs.

NeighborWhat it isWhy it is not this post
Leftover allowed-toolsA skill grant that survived into a later turn after you left plan or autoGrant lifetime versus path spelling, already shipped
Repeated reply IDA phone yes that reused an IDChannel verdict versus UNC read
Sandbox auto-allow on equalsAn every-run prompt on python3 -cMatcher versus network path
Redirected rmA redirected remove treated as always-ask offShell rewrite, already shipped
Nested denyA nested deny treated as a mod approvalPolicy tree, already shipped
/ultrareview staged copiesSandboxed commands reading staged uploads under ~/.claude/seed-adminSame-day leftover social, not this slug

Do not steal those as a second heading. Do not clone A Leftover Allowed-Tools Rule Is Not You Still in Plan Mode or Print the Redirect Before You File Always-Ask Off. GSC this week still has no striking-distance query that asks for another leftover-allowed-tools refresh. This unused URL is the UNC read.

Neighbor fields: Leftover allowed-tools, Redirected rm, UNC path read marked this ticket

What you must not do

Forbidden:

  1. File “always-ask is off” or “the sandbox already allowed it” without printing the Read spelling, UNC versus local, and one human name.
  2. Put 2.1.292, 2.1.295, or 6 October 2026 in the title as a version-style hook. The numbers are evidence after the decision.
  3. Write a UNC-read or sandbox-bypass recipe: forging a local spelling over a share, stripping the network-path prompt, mapping a way around PreToolUse, or teaching auto mode to treat \\server\share as the working directory.
  4. Recommend buying a plan, a 20X badge, or a seat because a share was read quiet. Official permissions page lists working directories and --add-dir as a path-shape rule, not a purchase ticket. [Source: https://code.claude.com/docs/en/permissions]
  5. Treat leftover allowed-tools, redirected rm, nested deny, sandbox-equals, or /ultrareview staged copies as this field.
  6. Mix this field with allowed-mail, ghs_ length, lockfile, or a Copilot local-sandbox heading. Do not clone those shipped URLs.
  7. Mount the production share, or start a live auto-mode session against it, to demo the miss.
  8. Let a coding agent own CLAUDE_CODE_OWNER, or collapse a UNC read and a local allow into one ticket.

Allowed:

  1. Print the Read spelling. Redact the host if a log leaked a share you cannot store.
  2. Classify synthetic fixtures with classify_path_shape.py. Do not execute them against a live share.
  3. Scan logs for Read( plus \\, //, or /net/.
  4. Name one human as CLAUDE_CODE_OWNER.
  5. Keep UNC reads, mapped-drive --add-dir misses, and leftover allowed-tools as separate tickets.
  6. After the owner prints the four lines, treat the network-shaped quiet Read as this field. Official release: PreToolUse hook approvals and auto mode bypassing the prompt for network (UNC) path reads is the named miss. Official permissions docs: a UNC argument prompts; most UNC shares cannot be working directories. [Source: https://github.com/anthropics/claude-code/releases/tag/v2.1.292] [Source: https://code.claude.com/docs/en/permissions]
  7. Put one line in the agent instructions file you already own: when the agent reads a path, it prints the spelling class and refuses to treat a UNC Read as a local allow. Check the printed ticket. Do not trust the agent to follow the line.

If you need the broader habit, start at /ai-agent-operations/. Tooling notes live under /developer-tools/. Laravel plus Vue notes live under /laravel-vue-saas/. A first-week map is at /start-here/.

A changelog bullet about UNC reads is not permission to skip the four lines.

What you should do Monday morning

  1. Open the repo that actually runs Claude Code on a machine that can see a share. Export CLAUDE_CODE_OWNER to a human name. Run the ticket printer against yesterday’s agent log. Write this_field=True or False on the ticket next to that name.
  2. For every this_field=True row, answer in one sentence: the approved path was local and the Read was UNC, or both were local, or both were UNC, or the log never printed a spelling. If you cannot answer, the ticket is “owner missing,” not “always-ask is off.”
  3. Print UNC versus mapped drive versus local. If the path is a mapped drive, official docs still say pass that drive with --add-dir; that is not this slug. If the path is UNC or /net/ after a local yes, or after auto mode, official release notes name that miss. Split those tickets. [Source: https://code.claude.com/docs/en/permissions] [Source: https://github.com/anthropics/claude-code/releases/tag/v2.1.292]
  4. Confirm coding-agent instructions on this desk name the same owner and forbid “always-ask is off” without the four lines. Forbid mounting a production share to demo. Forbid writing a hide path that turns a UNC Read into a local allow.
  5. Confirm PreToolUse still uses the documented shape: a deny is a deny, silence is not an allow, the call then hits the normal permission flow. Do not invent a dashboard that mints network allows. Do not display a fake quiet-prompt screenshot as proof. [Source: https://code.claude.com/docs/en/hooks]
  6. Leave leftover allowed-tools, repeated reply ID, sandbox-equals, redirected rm, nested deny, allowed-mail, ghs_ length, lockfile, and /ultrareview staged copies off this ticket. Those are neighbor fields. Do not steal them as a second heading.

The question is not whether the changelog demos well. The question is whether the named owner can still tell a UNC path read from a local allow after handoff.

Further reading

Source GitHub — Claude Code v2.1.292 release notes

Source Claude Code Docs — Permissions (network / UNC paths)

Source Claude Code Docs — Hooks (PreToolUse is not a silent allow)