
Fifteen years building engineering platforms, currently focused on advanced AI infrastructure at yeet. I love turning the deeply complex topics into something everyone can understand. I relate deeply with the core yeet philosophy that you can just build things.
Last updated: September 2026
Quick answer. Claude Code runs where you run it, so "investigating production" means it reaches out over SSH, through a bastion, or through an MCP server, and the safety work splits in two. Inside Claude, use
dontAskmode, which Anthropic documents as auto-denying "every tool call that would otherwise prompt you", because a mode that fails closed is the only kind that works when nobody is watching the terminal. Put that policy inmanaged-settings.jsonrather than a developer'ssettings.json, because managed settings apply "above every other level, so no user, project, local, or--settingsvalue overrides them". Outside Claude, none of it constrains a subprocess once Bash spawns one, so the record of what actually ran has to come from the host.
I build kernel-side tooling and spend a lot of time watching what agents actually do on machines, which is a useful vantage point for exactly one half of this question and no help at all with the other. I have not run your bastion and I am not going to tell you whether Claude belongs near your production estate. What I keep seeing is teams doing careful work on one half and nothing on the other: either a beautifully scoped SSH account with a Claude session running in a mode that approves everything, or a locked-down permission config on a box where nobody can say afterwards which process wrote the file.
It can, because Claude Code runs shell commands and ssh is a shell command, and that is the part worth pausing on. There is no special remote mode to enable and no integration to configure, which means there is also no separate place to go and turn remote access off. If the machine running Claude has a key that reaches production, Claude reaches production, and it does so through the Bash tool like any other command. The access question was decided when somebody put the key on that laptop, not when the session started.
Three routes people actually use, in rough order of how much they hand over:
restart_service rather than holding a shell. It is also the most work, and an MCP server is its own local process with its own access that deserves its own audit.The honest framing is that none of these is a permission model. They are transport. What decides whether Claude runs a given command is Claude's permission system, and what decides what that command can touch once it runs is the host. Those are two different systems and most teams configure exactly one, usually the one they find first. The tell is a team that can describe its permission config in detail and cannot say which Unix account the session lands in on the far side, or the reverse: a carefully scoped service account reached by a session running in a mode that approves everything.
dontAsk, plan or auto mode against production?dontAsk, and the reason is that it is the only mode that fails closed. Claude Code ships six modes, the ones people reach for are the wrong ones for an unattended session against a real system, and the difference between them is not how strict they are but which direction they break when something unexpected happens.
| Mode | What runs without asking | Fails |
|---|---|---|
default (Manual) | Reads only | Closed, but blocks on every prompt |
acceptEdits | Reads, file edits, common filesystem commands | Open for edits |
plan | Reads, plus classifier-approved commands | Closed, but it is for exploring, not acting |
auto | Everything, with background safety checks | Open, reviewed by a classifier |
dontAsk | Reads and pre-approved tools; anything that would prompt is denied | Closed |
bypassPermissions | Everything | Open, no checks at all |
The documentation on dontAsk is unusually specific about what it is for, and the use it names is not the one most readers arrive with. It says Claude Code "auto-denies every tool call that would otherwise prompt you", that the mode suits "CI pipelines or restricted environments where you pre-define what Claude may do", and that "the session never waits for input".
That last clause is the property that matters. An investigation session against production is exactly a session where nobody is going to be watching the prompt, and a mode that waits for approval nobody gives is a mode that stalls, while a mode that approves by default is one that acts unreviewed. dontAsk does neither: it runs what you pre-approved and refuses the rest, without stopping. The cost is real and worth stating: you have to know in advance what the investigation needs, and a denial mid-incident reads as the tool being broken rather than as the policy working. That is the trade every fail-closed control makes, and it is the right one when the alternative is an unattended session with approval turned off.
plan mode read-only enough for a production investigation?Not on its own, because plan mode is scoped to exploring a codebase rather than to protecting a system, and it is weaker than its name suggests in one specific case. Plan mode allows reads plus classifier-approved commands, which is genuinely useful for an investigation where you want Claude to look and propose rather than act.
The gap is documented rather than hidden, and it is worth knowing before relying on the mode's name. From the permission-modes page, writes to protected paths are never auto-approved:
"except in
bypassPermissionsmode and in plan-mode sessions where bypass permissions are available, meaning interactive terminal sessions started in a way that putsbypassPermissionsin the mode cycle"
So a plan-mode session in a terminal launched with bypass available is not the read-only thing the name implies. That is a narrow case and it is exactly the kind of narrow case that bites during an incident, when somebody launched the session with a flag six hours earlier for a different reason.
Plan mode is a good default for a human-supervised investigation, where somebody is reading the proposal before approving it and the classifier is a second opinion rather than the only one. For an unattended session, dontAsk with an explicit allowlist is the mode whose failure direction you can predict without knowing how the terminal was launched.
settings.json a preference and managed-settings.json a control?Because the developer can change it, which means it expresses an intention rather than a boundary. This is the single most useful distinction in Claude Code's configuration model and it is easy to miss, since both files have the same shape and the same keys.
Anthropic states the precedence plainly on the managed settings page, and the sentence is worth reading as a security property rather than as configuration trivia: managed settings are "the settings your organization deploys to every developer's machine", and "Claude Code applies them above every other level, so no user, project, local, or --settings value overrides them".
A managed-settings.json goes in a system location, /etc/claude-code/managed-settings.json on Linux and WSL, and it is deployed by whatever already puts files on your fleet, which for most teams means the same tooling that manages sudoers or an SSH config. That placement is the point: a file a developer can edit is a file a developer can widen, and a file under /etc deployed by fleet tooling is one they cannot. The documented example is short:
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./secrets/**)"
],
"disableBypassPermissionsMode": "disable"
},
"allowManagedPermissionRulesOnly": true
}
Three things are happening there and all three matter for production. The deny rules block reads regardless of mode. disableBypassPermissionsMode removes the escape hatch entirely, so the mode Anthropic warns offers no protection against prompt injection is simply not available. And allowManagedPermissionRulesOnly makes Claude Code ignore permission rules from user, project and local files, so a developer cannot widen the policy by adding their own allow rule. Without that third key the first two are a strong suggestion, because a local settings file can grant back most of what the managed file withheld.
bypassPermissions on a production box?Deny rules, and they are the only thing in the permission system with that property. The documentation states it directly: "Deny rules block in every mode, including bypassPermissions. Allow rules have no effect in bypassPermissions." Read those two sentences together and the design intent is clear enough to build on.
That asymmetry is the design worth building on. An allowlist is a convenience that evaporates in the most permissive mode; a denylist is a floor that holds everywhere. For a production investigation the practical shape is a short managed denylist of things no investigation ever needs, credential paths and destructive commands, plus a mode that fails closed, plus a narrow allow rule for the diagnostics you actually run.
Anthropic is also unambiguous about the mode people reach for when the prompts get tiresome, which is the moment most of these policies are actually tested: "bypassPermissions offers no protection against prompt injection or unintended actions." That is the vendor's own assessment of its most permissive setting, printed in a warning box rather than buried.
That warning is about one mode and it generalizes to the whole category. Anything that depends on the agent evaluating whether an action is reasonable is steering rather than enforcement, which is the same conclusion Replit reached after an agent deleted a production database during a code freeze: the control that held was a structural separation, not a better instruction.
yeet graph query instead of a shell on production?Yes, and it changes the shape of the permission problem rather than adding another rule to it. Install yeet on the box you want investigated and the daemon exposes the host as a typed GraphQL graph that the CLI queries:
yeet graph dump # the full schema, as SDL
yeet graph query '{ procs { pid comm cpu_time } }'
dump prints the schema, so Claude can discover what is available rather than being told, and query returns JSON on stdout, which is the shape an agent consumes best. The graph covers processes with command lines, CPU time, resident memory, disk I/O and scheduler run delay, plus per-core CPU, memory, network interfaces, containers, load average and kernel stats. That is most of what an investigation actually asks for.
The part that matters for permissions is structural. The schema is built as Schema<Query, EmptyMutation, Subscription>: there is no mutation type, so there is no query string that changes anything on the host. Read-only is not a policy you configured and can misconfigure, it is the absence of a code path.
Compare that to the string-matching problem above. Bash(rm *) has to anticipate /bin/rm and bash -c 'rm ...' and every other spelling, because a rule over command text is guessing at a program's names. An allow rule over yeet graph query has nothing to anticipate: the surface is the schema, the schema has no mutations, and the only thing the agent can express is a question. One allow rule covers the whole diagnostic surface without widening what the agent can do to the machine.
Bash(rm *) deny rule a boundary around rm on production?No, and Anthropic says so in the permissions documentation rather than leaving you to discover it. A Bash rule matches the command text Claude writes, after Claude Code splits compound commands and strips wrappers, which covers the invocation Claude usually produces and not the same program reached another way:
"It doesn't match the same program invoked in a different form, so a deny or ask rule covers the invocation Claude usually produces and isn't a security boundary around the program."
The documented examples are concrete. A Bash(rm *) rule stops rm -rf build/ and does not stop /bin/rm -rf build/ or bash -c 'rm -rf build/'. A Bash(curl *) rule stops curl https://example.com and does not stop /usr/bin/curl https://example.com. None of that is a bug; a rule over command text cannot be anything else, and the docs point you at sandboxing "for filesystem and network enforcement that doesn't depend on the command text".
Credit where it is due, because the surrounding machinery is better than most: Claude Code parses compound commands and requires each subcommand to match independently, so Bash(safe-cmd *) does not license safe-cmd && other-cmd, and the recognized separators include &&, ||, ;, | and newlines. Deny and ask rules apply when any subcommand matches, including inside a subshell, a command substitution or a loop body. This is careful work.
It is still string matching, and the production consequence is specific. A deny rule is a good way to stop the command Claude would naturally write, which is most of the value most of the time. It is not a way to guarantee a program never runs on that host, and the two get conflated in exactly the review meeting where somebody asks whether the agent can delete things.
ssh runs on the far side?No, and this is where the Claude-side work stops and the host-side work starts. Claude Code decides whether to run a Bash command. Once that command is running it is an ordinary process on an ordinary machine, and what it spawns is outside the permission system entirely.
Anthropic documents the observable consequence in its own monitoring docs: OTEL_* environment variables are not passed to the subprocesses the Bash tool spawns. So one approved Bash call is one event in Claude's record, whatever the process tree underneath it does, and the full treatment of that boundary covers what auditd and strace each catch.
For a production investigation this matters more than it does on a laptop, because the interesting commands are compound. ssh prod 'systemctl status app && journalctl -u app -n 200' is one approved tool call and several processes on a machine you care about. To see those, you need a record from the host:
curl -fsSL https://yeet.cx | sh
yeet login
yeet run gh:yeet-src/exectop -- --pid $(pgrep -n claude)
exectop folds every command in that process tree into one row per kind, following it through fork in the kernel. Where you run it decides which question it answers, and the useful placement is the one people skip. On the laptop, scoped to the Claude process, you see the ssh invocations and nothing about what they did. On the production host, unscoped or scoped to the sshd session, you see the commands that actually executed there, which is the thing neither Claude's transcript nor its permission rules can tell you.
That second placement is also what closes the string-matching gap above. A deny rule cannot promise /bin/rm never ran; a kernel record can tell you whether it did. The two are complementary rather than redundant: the rule shapes what Claude writes, and the record covers every form the rule could not name. Attaching to a running pid cannot see children forked before you attached, which the tool states rather than papering over, so start it before the session rather than after something looks wrong.
managed-settings.json before Claude touches production?Start with the managed file, because it is the only part a developer cannot undo, then scope the credential, then arrange the record. That order is deliberate: each step is cheaper than the one before it to get wrong, and the first one is the only one that survives somebody deciding the prompts are slowing them down. In rough order of how much each buys:
managed-settings.json with a denylist of credential paths, disableBypassPermissionsMode, and allowManagedPermissionRulesOnly. This is the control; everything else is configuration.dontAsk as the mode for unattended sessions, with an explicit allow rule for the diagnostics you expect. The session never blocks and never improvises.auditd with an execve rule if it must survive a reboot, a kernel-side process-tree probe if you want it now.Split the work before you start, because Claude's permission system and your host's controls answer different questions and neither covers the other. Inside Claude, the thing that actually holds is a managed-settings.json a developer cannot override, carrying deny rules that survive every mode, disableBypassPermissionsMode, and allowManagedPermissionRulesOnly; pair it with dontAsk so an unattended session fails closed rather than stalling or improvising. Outside Claude, decide what the credential reaches, because a permission mode governs whether Claude runs ssh and nothing about what ssh can log into. And whichever transport you pick, keep a record the agent did not write, which is auditd when it has to survive a reboot and exectop on yeet when you want the process tree on one box right now.
The failure worth naming is doing the first half well and calling it done. A carefully written permission config governs the commands Claude decides to write, which is most of the value most of the time, and Anthropic is direct that a rule over command text "isn't a security boundary around the program". Everything past the first Bash call is a process on a machine, in whatever form it was invoked, and the only thing that sees it is the machine.
Yes, by running ssh as an ordinary shell command through its Bash tool. There is no separate remote mode to enable, which means the question is not whether Claude can reach production but what the credential on that machine reaches, and whether the permission mode in use would approve the command.
dontAsk, because it auto-denies every tool call that would otherwise prompt, so an unattended session neither stalls waiting for approval nor proceeds without it. Anthropic documents it for CI pipelines and restricted environments where you pre-define what Claude may do.
Close, with a documented exception. Writes to protected paths are never auto-approved except in bypassPermissions mode and in plan-mode sessions where bypass permissions are available, meaning interactive terminal sessions launched in a way that puts bypass in the mode cycle. For unattended work, dontAsk has a more predictable failure direction.
Ownership and precedence. A developer writes settings.json and can change it at any time; an administrator deploys managed-settings.json to a system path, and Claude Code applies it above every other level so no user, project, local or --settings value overrides it. Only the second is a control.
Yes. Deny rules block in every mode including bypassPermissions, while allow rules have no effect in that mode. That asymmetry makes a short managed denylist more durable than any allowlist, which is why credential paths belong in deny rules rather than being omitted from an allowlist.
Yes, by setting permissions.disableBypassPermissionsMode to "disable" in managed settings. Anthropic's own warning is that the mode offers no protection against prompt injection or unintended actions, which is reason enough to remove it from machines that can reach production.
It records the tool call rather than the process tree underneath it, and Anthropic documents the mechanism: OTEL_* environment variables are not passed to the subprocesses the Bash tool spawns. A compound command is one event in Claude's record and several processes on the machine.
An MCP server exposing named operations is the narrowest of the common routes, because the agent calls a defined tool rather than holding a shell. It is also more work to build, and the server is itself a local process with its own filesystem and network access, so it needs auditing separately rather than being trusted by virtue of being a tool.
Check rather than assume, because an undelivered managed file and a delivered one look identical from the developer's side until a policy needs to hold. Claude Code's managed settings documentation covers verification, and the practical habit is to confirm enforcement on a machine before that machine is allowed near production.
Not by itself, and Anthropic says so for the most permissive mode: bypassPermissions offers no protection against prompt injection or unintended actions. Deny rules and a fail-closed mode reduce what an injected instruction can accomplish, but the durable controls are the ones outside the agent's reasoning entirely.
dontAsk auto-denying "every tool call that would otherwise prompt you" and being intended for "CI pipelines or restricted environments", for the protected-paths exception that applies to plan-mode sessions with bypass available, for deny rules blocking in every mode while allow rules have no effect in bypassPermissions, and for the warning that bypassPermissions offers no protection against prompt injection./etc/claude-code/managed-settings.json, and for the worked example combining deny rules, disableBypassPermissionsMode and allowManagedPermissionRulesOnly.&&, ||, ;, | and newlines recognized as separators. Also the source for the limit quoted above, that a rule "isn't a security boundary around the program", and for the worked table showing Bash(rm *) stopping rm -rf build/ but not /bin/rm -rf build/ or bash -c 'rm -rf build/'.lsm/file_open that refuses opens outside a named directory, for bounding where a session can write rather than only recording it.Built with yeet, a JS runtime for writing eBPF programs on Linux machines. Join us on discord.