Skip to content

fix(runner): fail closed when the Pi permission extension cannot install#5318

Merged
mmabrouk merged 1 commit into
big-agentsfrom
fix-pi-permission-failclosed
Jul 14, 2026
Merged

fix(runner): fail closed when the Pi permission extension cannot install#5318
mmabrouk merged 1 commit into
big-agentsfrom
fix-pi-permission-failclosed

Conversation

@mmabrouk

Copy link
Copy Markdown
Member

Context

Setting a permission policy that should block or ask before a tool ran did nothing on the published runner image. A user could set the Pi policy to deny (or ask), send a message that runs a shell command, and the command still ran with no prompt. The user got no signal that their policy was a no-op.

The cause was a fail-open. Pi enforces the policy through a small extension the runner installs into Pi's agent directory at the start of each run. The published image runs as the unprivileged user node (uid 1000), but the image never created /pi-agent (the deployment's configured PI_CODING_AGENT_DIR) or granted node write access to it. So mkdir /pi-agent/extensions failed with EACCES. The install caught that error, logged one line, and returned void. The session then continued with no enforcement. Because the default policy is allow_reads (not allow), gating is active on nearly every run, so the blast radius was wide.

Changes

Three parts, all in the runner.

  1. Image. Dockerfile.gh now creates /pi-agent owned by node before the USER node line, so the install succeeds on a fresh pull of the default path.

  2. Writable-directory routing. Managed-key and no-key local Pi runs now install the extension into a per-run temporary directory the runtime user owns (under the system temp path), instead of straight into the configured /pi-agent. This generalizes the temp-dir approach the runner already used for runs carrying custom skills or a system prompt, so the install no longer depends on any operator-set directory being writable. The subscription path is unchanged: it must run out of the operator's mounted login so Pi can refresh its token mid-run.

  3. Fail closed. The install functions now report success instead of returning void:

    Before: installPiExtensionLocal(agentDir): void (swallowed the failure)
    After: installPiExtensionLocal(agentDir): boolean (false when it could not install)

    uploadPiExtensionToSandbox gains the same boolean. In session setup (acquireEnvironment), when the policy could gate a Pi built-in tool (builtinGatingActive) and the extension did not install, the run throws a named error constant. The engine's existing catch turns it into { ok: false, error } and a visible error frame, following the TOOL_MCP_UNAVAILABLE_MESSAGE pattern. When the policy is allow-everything the extension is not needed, so a failed install stays harmless and the run continues.

    The error names the cause, states that nothing ran unprotected, and points the operator at the remedy: "The agent could not enforce its permission policy: the permission component failed to install, so no built-in tool could be gated. The run was stopped so no tool ran outside your policy. Ask your deployment operator to make the runner's Pi agent directory writable, or rebuild and republish the runner image."

Tests / notes

  • Added a pi-assets unit test: an unwritable extension bundle makes the install functions report failure.
  • Added pi-permission-failclosed.test.ts: gating active plus a failed install returns { ok: false } with the new message; an allow-everything policy plus a failed install continues to sandbox startup.
  • Full runner suite green (pnpm test, 1158 tests) and pnpm run typecheck clean.
  • The shared unit setup now points SANDBOX_AGENT_EXTENSION_BUNDLE at a stub file, because CI does not run build:extension before the unit suite and the new fail-closed path would otherwise trip on the absent artifact.

Rollout

Runs on an old, unpatched image now fail loudly with the remedy in the error message, instead of running unprotected. This is the point of the fix. Because gating is active on nearly every default run, the image fix and the temp-dir routing ship together so the install succeeds in the common cases and fail-closed only fires when a directory is genuinely unwritable. The image change takes effect once the runner image is rebuilt and the published charts point at it.

The related Claude built-in gap (the "Deny all" control not governing Claude's own shell) is a separate change in a different file and ships in its own PR.

https://claude.ai/code/session_01Hyn9365BLPXDmNZShrQkmH

@mmabrouk

Copy link
Copy Markdown
Member Author

@coderabbitai review

@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jul 14, 2026
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jul 14, 2026 7:17pm

Request Review

@dosubot dosubot Bot added the Bug Report Something isn't working label Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b85fb6b1-1ac6-4b52-a165-1cca889f74ee

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-pi-permission-failclosed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-07-14T19:29:37.701Z

@mmabrouk
mmabrouk changed the base branch from main to big-agents July 14, 2026 18:58
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Jul 14, 2026

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

The published runner image runs as user node (uid 1000) but never created
/pi-agent (the configured PI_CODING_AGENT_DIR), so installing Pi's
permission-enforcing extension failed with EACCES. The install swallowed
that error and returned void, so the runner continued with NO enforcement:
a deny/ask policy silently ran every built-in tool.

Three parts:
- Image: create /pi-agent owned by node in Dockerfile.gh before USER node.
- Routing: managed/no-key local Pi runs install the extension into a per-run
  temp dir the runtime user owns, so the install no longer depends on the
  configured agent dir being writable.
- Fail closed: installPiExtensionLocal / uploadPiExtensionToSandbox now report
  success; session setup throws a named error when the policy could gate a Pi
  built-in and the extension did not install, surfaced as an error frame.

Claude-Session: https://claude.ai/code/session_01Hyn9365BLPXDmNZShrQkmH
@mmabrouk
mmabrouk force-pushed the fix-pi-permission-failclosed branch from 100e8fe to 6ae2d91 Compare July 14, 2026 19:16
@mmabrouk
mmabrouk merged commit 1b5aec5 into big-agents Jul 14, 2026
35 of 36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Report Something isn't working size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant