Build1 distinct publisher3 min readUpdated
A PreToolUse hook on an AI coding agent returned "allow" for every file under a non-ASCII directory name, for weeks, with no log line and no exception.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
A local policy guard wrapped around an AI coding agent spent part of several weeks returning "allow" for every file that sat under a directory whose name was not ASCII, and it never logged, raised, or exited nonzero while doing it [5][6][7]. According to the author's writeup on dev.to, the durable lesson is not the encoding bug but the interface: a hook whose only output channel is an exit code has no way to say that it could not reach a decision [12].
The setup is conventional. Before the agent performs an action, the proposed tool call is handed to a small Python script as JSON on stdin, and the script answers with one of two exit codes: 0 to allow, 2 to block and return the reason to the agent as feedback [1][2]. The author describes four such guards: one refusing credential paths, one intercepting destructive shell commands, one enforcing a directory boundary, and malformed-read-guard.py, which blocks reads of files containing corrupted tool-call syntax because the model starts emitting that syntax itself and the session locks up [3][4]. One of the four was silently inert [20].
The failure composes from three individually reasonable decisions. Hook input is always UTF-8, but on Windows Python opens sys.stdin with the locale encoding, cp932 on that machine, so json.load(sys.stdin) decoded UTF-8 bytes as cp932 [8]. cp932 is permissive enough that those bytes map onto some sequence of characters, so there is no UnicodeDecodeError to catch and log, only a wrong string that travels onward as valid data [9]. The guard then built a Path and returned exit 0 if the target was not a file, which is the correct behaviour when there is nothing to inspect [10]. A mangled path is indistinguishable from a missing one, so that valve fired on every non-ASCII path in the system [11]. The affected class, as the author puts it, is any project whose folders are not named in English [17].
The measurements are blunt. Under Python 3.14.2 with host stdout encoding cp932, the same file at an ASCII path arrived intact and exited 2, blocked; under a Japanese-named directory the path did not survive decoding and the guard exited 0 [13]. After reading stdin as bytes and decoding UTF-8 explicitly, both cases exited 2 [14]. The fix is one line and the matching logic was untouched: read sys.stdin.buffer, decode with errors="replace", exit 0 only on empty input or a JSONDecodeError [16].
What makes this an operator problem rather than a Windows trivia item is the observability. Exit 0 means allowed and also means ran fine, with no third value for "I could not tell", so every signal said the guard was healthy [12]. At the call site, a guard that skipped its check is byte-identical to a guard that approved [19]. An ASCII-only test suite goes green on a dead guard, and the author notes there was no failing test to write because the test they would have written passed [15].
Two things worth checking in your own hooks. First, whether any guard can express a third outcome, and what the agent does when it sees one. Second, whether the fixture set contains a single directory named in a non-Latin script; the author's own fix comment records the measurement date and is written in Japanese, because the codebase is [18].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The guards are PreToolUse hooks: before the AI coding agent is allowed to perform an action, the proposed tool call is handed to a small Python script as JSON on stdin.
The hook contract is two exit codes: exit 0 means allow, exit 2 means block and send the reason back to the agent as feedback.
The author runs several guards: one refuses access to credential paths, one intercepts destructive shell commands, one enforces a directory boundary, and one is malformed-read-guard.py.
malformed-read-guard.py blocks the agent from reading files that contain corrupted tool-call syntax, because reading that syntax makes the model start emitting it too and the session locks up.
The guards had been working for weeks, and one of them had also, for some of that time, been doing nothing at all.
The same file with the same bytes was blocked with exit 2 when placed at an ASCII path, and allowed with exit 0 when placed under a directory whose name contains Japanese characters.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Reproduced first-hand, never independently
Unusually concrete for a single anecdote: the post pins the interpreter (Python 3.14.2), the host and stdin encodings (cp932), pastes unedited before/after console output for both the ASCII and Japanese path cases, shows the offending line and the one-line replacement, and preserves a dated in-file measurement. What holds the score down is that all of it is first-party from one machine in one locale, with no repository, commit or artefact a reader can re-run, and no independent replication anywhere in the cluster; the mechanism is stated as fact rather than demonstrated against other Windows locales or platforms.
One first-party install
The only documented uptake is the author's own environment: two hooks (malformed-read-guard.py and security-guard.py) patched the same day with the explicit-UTF-8 pattern, plus a regression comparison of the security guard against its previous behaviour. No other users, teams, projects, downstream repositories or upstream framework changes are reported, and the cluster gives no way to size how many agent-hook installs share the defect, so adoption is real but confined to a single developer's local setup.
Slightly understated
The framing tracks the demonstrated facts closely: the title and dek describe exactly what the pasted output shows (a guard that answers only in exit codes cannot report that it stopped guarding), and the fix is presented as one line rather than as a novel technique. If anything the post undersells itself — a silently inert safety control on an AI coding agent, in a locale-dependent decode path that an ASCII-only test suite cannot catch, is a class-level failure mode, yet it is told as one engineer's contest bug story with no generalised advisory, no request for others to check their hooks, and no claim about scope beyond 'folders aren't named in English'. Nothing in the post is inflated beyond its evidence, so the gap is small.
Disclosed contest submission, low commercial stake
The post discloses in its first line that it is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry, so there is a visibility and prize incentive to make a debugging story vivid and self-contained, and the piece is published under a lab-branded author handle on a developer-marketing-friendly platform. Offsetting that: the author is reporting a failure in their own safety tooling, sells nothing, names no vendor product as the remedy, and the fix is a stdlib one-liner rather than an argument for buying anything. The incentive shapes packaging and confidence of narration more than it shapes the technical claims.
Plausible mechanism, single unreplicated report
The causal chain is internally coherent and each step is exhibited rather than asserted, and the before/after control isolates a single variable, which supports moderate confidence in the specific finding. But the cluster has one publisher, one author, one machine, one locale and no external verification, no linked artefact, and no statement from the agent runtime's maintainers about hook stdin encoding; the duration claim ('weeks') rests on recollection alone. That combination caps confidence around the middle: the described behaviour is credible and actionable for anyone on a similar Windows/cp932 setup, but its prevalence elsewhere is unestablished.
build
The duplicate def that ate the trim, and the lint rule nobody was running1 distinct publisher
build
A Stripe SDK Major Bump Turned One Metadata Lookup Into a Silent Non-Delivery1 distinct publisher
build
py-libp2p bounds a dev-only /sdp endpoint that believed whatever Content-Length it was told1 distinct publisher
build
Your script exited 0 because the file no longer existed: macOS evicted it to iCloud1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026