Files
PaperClipAI/packages/adapter-utils
AlexFrontegg 57658fcb30 fix(codex): pass the run's GitHub broker capability to shell commands (#17605)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - When an execution has managed GitHub, Paperclip gives it token-free
`git` and `gh` launchers. Each launcher call gets the run's credential
from the broker with a per-run capability
(`PAPERCLIP_GITHUB_BROKER_TOKEN`).
> - The launchers run inside the agent's shell commands and read the
capability from their environment.
> - #17216 limits the environment of Codex shell commands to an
allowlist, and it keeps every `PAPERCLIP_GITHUB_*` name out.
> - So on legacy Codex through ACP, the launchers do not get the
capability, report `capability_missing`, and continue without
credentials.
> - The native runner already passes the same names to its Codex
provider (`GITHUB_CREDENTIAL_ENVIRONMENT_KEYS` in
`packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs`).
> - This pull request adds the broker capability to the legacy Codex
shell allowlist when Paperclip set it for the run.
> - The benefit is that managed `git` and `gh` work again for legacy
Codex agents.

## Linked Issues or Issue Description

Fixes #17599
Refs #16549, #17216

## What Changed

- `packages/adapter-utils/src/codex-shell-environment.ts`: the shell
allowlist also includes `PAPERCLIP_GITHUB_BROKER_TOKEN` and
`PAPERCLIP_GITHUB_BRIDGE_TOKEN` when they have a value in the run's
environment. Only the names enter
`shell_environment_policy.include_only`. The values stay out of the
Codex config and the arguments.
- `packages/adapter-utils/src/codex-shell-environment.test.ts`: a new
test for the capability. The "runtime transports" test no longer lists
the broker token, because the launchers need it in the shell.

## Verification

- `pnpm --filter @paperclipai/adapter-utils exec vitest run
src/codex-shell-environment.test.ts`: 5 passed. The new test fails
without the change.
- Before the change, on a deployment with a legacy Codex agent that has
managed GitHub, `gh pr view` in a shell command fails with `Paperclip:
GitHub capability_missing; continuing without managed credentials`, and
the shell has the broker URL but not the broker capability. I will add
the result after the change is deployed there.

## Risks

- Codex shell commands can now read the run's broker capability. The
broker accepts it only while the run is live (`captureRunIdentity`
requires `status === "running"`), and it returns the credential that the
managed `gh` command receives anyway.
- Agents cannot set these names in their configuration.
`stripForbiddenEnvBindings` removes them
(`server/src/services/heartbeat/run-preparation.ts`) before the server
adds the run's own values.
- No change for runs without managed GitHub: Paperclip does not set the
capability, so the allowlist does not add it.

## Model Used

- Provider and model: Anthropic Claude Opus 5.5 (`claude-opus-5-5`),
used through Claude Code.
- Capabilities used: tool use, code execution (local tests and type
checks), and repository search.
- A human reviewed the diagnosis and the change before this pull request
was opened.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [ ] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
2026-10-29 13:18:49 -07:00
..

@paperclipai/adapter-utils

Shared utilities for Paperclip adapters: process spawning, environment injection, sandbox/SSH transport, workspace sync, and the round-trip helpers that move code between the local execution-workspace cwd and wherever the agent actually runs.

For the adapter-author guide see docs/adapters/creating-an-adapter.md and the in-repo notes at packages/adapters/AUTHORING.md.

Sandbox bridge deadlines

Command-managed bridge control operations (queue reads, input delivery, setup, and cleanup) have a host-enforced deadline of at most 30 seconds per shell command. A shorter configured timeout still applies. These operations cannot inherit the agent run's hours-long lifetime or wait forever on a provider that ignores its timeout. Long-lived agent session commands keep their own limits.

A control timeout reports a transport failure. It does not prove that the remote command stopped and does not replay an uncertain write. The process session closes failed input delivery and uses its existing shutdown path; normal execution settlement must still verify termination.

No-remote-git contract

The local execution-workspace cwd is the only persistence boundary across runs. No adapter may depend on a git remote for cross-run state.

Adapters that run the agent on a different host should use the SSH round-trip helpers in src/ssh.ts:

  • prepareWorkspaceForSshExecution({ spec, localDir, remoteDir }) — bundles the local cwd (tracked files, dirty edits, untracked additions, and the git history needed to reconstruct it) to remoteDir before the run starts. Runs with no git remote configured.
  • restoreWorkspaceFromSshExecution({ spec, localDir, remoteDir, ... }) — syncs the remote cwd back into localDir after the run, including any new commits the agent created. Also runs with no git remote configured.

prepareRemoteManagedRuntime in src/remote-managed-runtime.ts wraps both calls for adapters that want a per-run remote workspace and an automatic restoreWorkspace() finally hook.

The invariant is pinned by the no-remote-git contract case in src/ssh-fixture.test.ts, which asserts that a remote-only commit propagates to the local worktree through the prepare → restore round-trip with no git remote configured at any point. Do not regress that test.