Files
PaperClipAI/packages/adapter-utils
Devin FoleyandPaperclip 546cfee919 fix(github): send gh repo create and fork to the connection's tools (#20142)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Agents with a managed GitHub connection get `git` and `gh` launchers
that use the connection's credential
> - The launcher gives that credential to any `gh` command, and the
broker does not see which command runs
> - So `gh repo create` and `gh repo fork` skip the connection's tool
switches, ask-first approvals, owner defaults, and audit
> - The connection's `create_repository` and `fork_repository` tools
apply all of these
> - This pull request stops `gh repo create`, `gh repo new`, and `gh
repo fork` when a managed credential is in use, and names the tool to
use instead
> - The benefit is that repository creation follows the connection's
rules, whichever path the agent tries first

## Linked Issues or Issue Description

Refs #20141 (default owner for `create_repository` and
`fork_repository`).

**What happened?**
An agent with a managed GitHub connection can run `gh repo create` in
its shell. The command uses the connection's token, but it does not use
the connection's tool rules. If the user turns off or reviews
`create_repository` on the connection, the agent can still create a
repository with `gh`. The owner default from #20141 also does not apply,
so the command can fail with `403 Resource not accessible by
integration` when the App is installed only on an organization.

**Expected behavior**
With a managed connection, repository creation and forks go through the
connection's tools. The tool switch, approvals, owner default, and audit
apply.

**Steps to reproduce**
1. Connect GitHub with a managed connection.
2. Set the connection's `create_repository` tool to off or ask first.
3. In an agent run, run `gh repo create demo --private`.
4. The command runs with the connection's credential and ignores the
tool setting.

## What Changed

- `packages/adapter-utils/src/github-launcher.ts`: when the broker
returns a managed credential, `gh repo create`, `gh repo new`, and `gh
repo fork` exit with code 1 before `gh` starts. The message names the
`create_repository` or `fork_repository` tool and tells the agent to
finish with Git.
- `--help` still runs. Other `gh` commands do not change. Executions
without a managed credential run these commands as before.
- `doc/execution-github-identity.md`: describes the behavior and states
that this is guidance, not a security boundary.

## Verification

- New test in `packages/adapter-utils/src/github-launcher.test.ts`:
"sends repository creation and forks to the connection's tools when a
connection is in use". It covers `create`, the `new` alias, `fork`,
`--help`, another `gh repo` command, and the case without a connection.
It fails without the launcher change.
- `npx vitest run src/execution-target.test.ts
src/github-launcher-environment.test.ts src/github-launcher.test.ts` in
`packages/adapter-utils`: 83 passed.
- `npx vitest run src/drivers/acpx/github-launcher.test.ts` in
`packages/paperclip-runner`: 3 passed.
- `npx vitest run src/services/heartbeat-github-launchers.test.ts` in
`server`: 12 passed.
- `npx tsc --noEmit -p packages/adapter-utils` passes.

## Risks

- Agents that used `gh repo create --source . --push` must now call
`create_repository`, then add the remote and push with Git. The message
tells them to do this.
- If the connection's tool is off for the agent, the agent cannot create
a repository. This is the purpose of the tool setting.
- This is not a security boundary. Other commands that receive the
token, such as `gh api`, can still call the same GitHub endpoints.
- Running launchers keep their earlier source until the next run stages
new launchers.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (code reading, shell commands, and test runs).

## 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)
- [x] 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
- [x] 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
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-08 17:08:35 -08: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.

Native archive transfer deadlines

Native syncIn and syncOut calls accept an optional minimumTimeoutMs budget. The server validates this budget and extends the worker RPC deadline. Providers can honor this field for file transfers without changing their configuration. Daytona uses the larger of this minimum and its configured timeout. Other providers keep their existing behavior. The minimum is limited to 30 minutes and does not change sandbox lifetimes, stored settings or the timeout for other commands.

The remote-workspace archive path uses the verified capture size for downloads and the local archive size for uploads. Large archives get one second per MiB plus 60 seconds, capped at 30 minutes. Small archives keep the existing provider timeout when that calculation is no greater than five minutes. Archive size, checksum, confinement and host space checks still apply.

An exact retained-sandbox resume keeps its recorded provider timeout and RPC overhead. It uses the normal one-minute fallback only when no timeout was recorded. A five-minute cold restore must not be cut off by that fallback. Resume still targets the recorded allocation and does not acquire a replacement.

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.