Skip to content

hook: prefer-terraform-chdir (avoid cd+redirect gate); carry no-avoidable-cd + xargs wrapper - #6

Open
joshuaLW wants to merge 3 commits into
laurenkt:masterfrom
joshuaLW:readonly-permission-hooks-2
Open

joshuaLW wants to merge 3 commits into
laurenkt:masterfrom
joshuaLW:readonly-permission-hooks-2

Conversation

@joshuaLW

Copy link
Copy Markdown
Contributor

Three follow-ups to the read-only permission-hook chain, all aimed at turning avoidable manual-approval prompts into analyzable commands that match an allowlist.

prefer-terraform-chdir (new PreToolUse/Bash handler)

A cd <dir> && terraform ... >log trips Claude Code's built-in "cd with an output redirect" gate — it can't resolve the redirect target against the post-cd working directory, so it forces a manual approval prompt.

This handler blocks that pattern and points at terraform's own -chdir=<dir> flag. With the cd gone the gate never fires, the redirect is harmless on its own, and the command can match a terraform allowlist and run with no prompt.

It's conservative:

  • Only fires when every non-cd segment is terraform (or an inert passenger like echo/printf), so a cd that a later step genuinely needs (a script, a relative path, make, dbt) is left untouched.
  • Requires an output redirection to be present — without one, cd <dir> && terraform ... doesn't trip the gate, so there's nothing to fix.
  • Skips a cd whose target already equals the working directory, leaving that to strip-redundant-cd.

Includes a table row in the README and full block/allow/skip test coverage.

no-avoidable-cd and the xargs wrapper

These two commits were already on the branch (they post-date the last merge to master) and are carried along:

  • no-avoidable-cd — nudges cd <repo> && git ... toward git -C <repo> ... / gh ... -R <owner/repo>, which avoids the "changes directory before running git" gate.
  • auto-approve: treats xargs as a wrapper and recurses into the wrapped command.

Testing

go test ./... passes. The new handler was also verified end-to-end against the real chain with the motivating command (a terraform fmt/init/validate compound with a redirect on init); it blocks with the -chdir guidance and the concrete directory extracted from the cd.

🤖 Generated with Claude Code

joshuaLW and others added 3 commits July 31, 2026 16:38
xargs was unrecognised, so read-only pipes like "find ... | xargs grep ..."
fell through to Claude Code's manual-approval gate. Treat xargs like the
existing execute-in-data-shell wrapper: skip its own flags (including the
value-taking ones such as -n/-I/-P/-d) and recurse segmentIsReadOnly into
the wrapped command, so "xargs grep/wc/cat" auto-approve while "xargs rm/mv"
and bare xargs still prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A leading "cd <repo> && git ..." trips Claude Code's cd-before-git
untrusted-hooks gate, whereas "git -C <repo> ..." matches a Bash(git *)
allow and runs with no prompt. The existing no-cd handler is a blanket
ban, which is too blunt (cd is sometimes genuinely needed).

no-avoidable-cd blocks only when a cd is combined solely with git/gh
plus inert passengers (echo, printf, pwd, true, false, :), and stays
silent the moment any segment could depend on the working directory
(scripts, dbt/adbt, make, relative paths). The block message tells the
agent to use git -C <dir> / gh -R <owner/repo> instead. It never
approves — only blocks.

Wire it into the hook chain after strip-redundant-cd in settings.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A `cd <dir> && terraform ... >log` trips Claude Code's built-in "cd with
an output redirect" gate (it can't resolve the redirect target against the
post-cd working directory), forcing a manual approval prompt.

prefer-terraform-chdir blocks that pattern and points at terraform's own
-chdir=<dir> flag. With the cd gone the gate never fires, the redirect is
harmless on its own, and the command can match a `terraform` allowlist and
run with no prompt.

It only fires when every non-cd segment is terraform (or an inert passenger
like echo) and an output redirection is present, so a cd that a later step
genuinely needs is left untouched. It also skips a cd whose target already
equals the working directory, leaving that to strip-redundant-cd.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@joshuaLW

Copy link
Copy Markdown
Contributor Author

Heads-up on how the no-avoidable-cd handler carried in this PR relates to no-git-c (added in #4): they take opposite positions and are mutually exclusive by design.

  • no-git-c blocks git -C <path> and steers to plain git …, on the premise that the command already runs in the working directory.
  • no-avoidable-cd blocks cd <repo> && git … and steers to git -C <repo> (and gh … -R <owner/repo>).

The difference is whether the repo you want to act on is always the session's working directory. In a single-checkout setup, no-git-c's "just run plain git" is right. In a multi-worktree setup — several sibling checkouts of the same repo, where the agent frequently needs to operate on a tree that isn't the current working directory — git -C <path> is the correct target and dropping -C would silently run against the wrong tree. That's the workflow no-avoidable-cd is for.

So these two shouldn't live in the same hook chain. Suggested framing: keep both available but document them as an either/or — pick no-git-c for single-checkout, no-avoidable-cd for multi-worktree. Happy to add a note to the README table making that explicit, or to drop no-avoidable-cd from this PR and land only prefer-terraform-chdir + the xargs change if you'd rather not ship the opposing handler upstream. Your call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant