How to stop a Claude Code agent writing outside a directory
A deep dive into security mechanisms for Claude Code when running unattended, featuring hooks and working tree diff checks to prevent unintended file modifications.

Stock photo for illustration only, not from the actual event
- Unattended agent execution requires automated write-blocking mechanisms that function without human supervision.
- Settings.json permission rules rely on block-lists rather than the allow-lists needed for unattended tasks.
- PreToolUse hooks accept JSON payloads via stdin to enforce strict tool execution boundaries.
- Combining hooks with post-execution git diff checks catches any modifications that slip past initial rules.
When working directly in front of an agent, enforcing rules like "don't touch anything outside src/" relies simply on human observation. Once left unattended, enforcement must rely on automated systems that execute whether anyone is watching or not.
Claude Code provides two distinct mechanisms for this purpose, and they are not interchangeable. The first option is declarative and cannot express the required permissions. The second option works but remains structurally blind to entire categories of write operations. Here is a breakdown of what each mechanism actually does and the code required for the second approach.
Permission rules reside inside settings.json using the format Tool(specifier). The fundamental challenge lies in shape: an unattended agent requires an allow-list permitting only specific directories and nothing else. Deny rules provide a block-list, and constructing an allow-list out of a block-list is impossible. The intuitive workaround of denying everything and explicitly allowing exceptions fails due to precedence rules where Write(**) in a deny rule outranks every paired allow rule, resulting in the agent being unable to write any files at all.
Claude Code includes one allow-list boundary—the project root combined with directories specified in additionalDirectories. This prevents an agent from wandering into /etc, but it offers no control over which directories inside the project workspace the agent may modify, which is usually the primary concern. The main anxiety is not that a scheduled agent will edit /etc/hosts, but rather that an agent tasked with writing articles might decide to modify its own scheduling configuration.

Stock photo for illustration only, not from the actual event
Therefore, managing scopes finer than the entire project requires custom code. A PreToolUse hook is a command executed by Claude Code prior to any tool call, passing the pending call as JSON through stdin. The hook's response is strictly binding.
This hook registers against file-writing tools, replying by exiting with status code 0 and outputting JSON via stdout. An alternative blocking method involves exiting with status code 2 and placing reasons on stderr, but this sacrifices structured fields, and status code 0 sends stderr solely to debug logs where neither operators nor models will observe it. JSON is strongly preferred.
Understanding permission boundaries in autonomous AI developer tools is critical as models gain increased file system autonomy. Relying solely on basic configuration files is often insufficient for overnight or unattended agent workflows. Implementing a defense-in-depth strategy by combining runtime input hooks with post-execution diff verification provides robust protection against agents drifting outside their intended operational scopes.
Path resolution logic must be implemented carefully. Utilizing string prefix checks such as "/app/src-secret".startsWith("/app/src") evaluates to true, which compromises allow-lists. Resolving both paths and checking whether relative traversal escapes using .. ensures robustness against sibling directories, ./ noise, and incoming path traversal attempts.
Source: Dev.to
Found something wrong in this article? Report an issue with this article
Comments
Leave a Comment