Why a CI Job Needs --bare, Not Just -p
Your team runs claude -p "summarize the failing tests" --output-format json
inside a GitHub Actions job that checks out a fork's PR branch (an
external contributor's fork, not your own repository) to generate a
test-failure summary as a comment.
- Without
--bare, what specifically can happen in this job that wouldn't happen in an ordinary interactiveclaudesession opened in a directory you've never trusted before? - Why doesn't Claude Code show the equivalent of a trust-dialog prompt
in this
-pinvocation the way it would interactively? - Rewrite the invocation to close this gap while still letting Claude read the test output and produce a summary.
1. What can happen without --bare:
A fork's PR branch is attacker-controlled content from Claude Code's
perspective — a malicious contributor could commit a .claude/settings.json
with hooks, or a .mcp.json pointing at a malicious MCP server, hoping
a CI job running Claude Code against their branch will execute it.
Without --bare, claude -p auto-discovers and runs a project's
.claude/settings.json hooks and connects .mcp.json servers exactly
as an interactive session would — meaning a fork's checked-out branch
can get its hooks or MCP servers executed inside your CI runner's
environment, with whatever access that runner has (secrets, network,
write access to your repository via the job's token).
2. Why no trust dialog in -p mode:
Interactive Claude Code shows a workspace-trust dialog the first time
you run it in a new directory, specifically so a human can look at
what's in .claude/ before anything in it executes. A -p invocation
has no interactive terminal to show that dialog in and no human
present to answer it — so rather than blocking indefinitely waiting
for an answer nobody can give, headless mode proceeds without showing
it, which is precisely why the auto-discovered content runs
unconditionally unless something else (namely --bare) prevents it.
3. The fixed invocation:
claude --bare -p "summarize the failing tests from the test output below" \
--allowedTools "Read" \
--output-format json
--bare skips auto-discovery of hooks, skills, plugins, MCP servers,
auto memory, and CLAUDE.md entirely, so nothing checked into the
fork's branch can execute as a side effect of running this command.
--allowedTools "Read" further scopes what Claude can do to reading
files — no Bash, no Edit — which is all a summarization task actually
needs. Since --bare doesn't read OAuth credentials or the system
keychain, ANTHROPIC_API_KEY needs to be set in the job's environment
(from a repository secret) for authentication to work at all.
Share this question