## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Native remote runners use a session-owned broker for managed GitHub operations. > - That broker uses the existing authenticated sandbox callback bridge. > - The native caller omitted the server-selected duplex flag. An enabled and capable target still used file polling. > - Warm sessions must observe a changed flag and channel loss before the next admission. > - This pull request forwards the existing flag and retires unavailable or changed bridges before retained instruction preparation. > - The benefit is less idle remote traffic without changing credential authority or the default transport. ## Linked Issues or Issue Description **What happened?** A native GitHub callback bridge selected `queue_v1` even when its sandbox target had duplex enabled and supported it. The file bridge continues remote queue checks while the warm session remains available. **Expected behavior** An enabled, capable target should use the existing HTTP/2 bridge. Disabled or unsupported targets and failed channel startup must retain the authenticated file fallback. A changed server flag must take effect at the next normal warm-session boundary. **Steps to reproduce** 1. Create native GitHub access on a sandbox target with the server-selected duplex flag enabled, verified duplex capability and a working channel. 2. Observe the selected callback transport. 3. Before this fix, it is `queue_v1`. The added real-wire regression expects `http2_v1` and fails on the old source. ## What Changed - Forward the existing server-selected duplex flag through the native GitHub broker. - Record that transport selection on the broker. Retire the warm provider and broker through the existing cleanup path when the flag changes. - Read live channel failure when checking broker readiness. Mark normal session cleanup as an orderly completion. - Include the selected flag in instruction preparation identity. Retire a failed broker before adopting retained instructions, while preserving existing ownership and cleanup fences. - Add real local HTTP/2 credential-rotation and file-fallback controls. Add native warm-session flag-change controls. - Document selection, fallback and warm-session behavior. - Scan CI secret expressions in one forward pass, including quoted braces and escaped apostrophes. Preserve direct, bracket, multiline and whole-context references while rejecting filename and literal-string false positives. ## Verification - Current head `41440b07ea45e9e882e36ed495a66e501c8dd4df` resolves the actual CI-policy conflict with master `307eb16c0c59abd9e0626fde9201939b262e7f5f`. It retains upstream's stricter whole-expression credential guard, including quoted secret text, and uses the forward scanner instead of a backtracking regex. All **16 CI policy tests** pass, including the bounded 28- and 10,000-apostrophe controls in both policy modes. The reported old regex still has its recorded two-second timeout (14 passed, one failed). On this final merged source, **809 affected native cases** pass with zero selected skips (65.20s). Full repository typecheck (31.44s) and build (22.13s) pass. Frozen source hashes stayed unchanged. Fresh exact-head full CI, configured security and completed Apex remain pending. - Before master integration, the frozen native repair passed repository `pnpm -r typecheck` passed (33.0s) and `pnpm build` passed (32.1s). - Before the latest master integration, all **800 affected cases passed with zero selected skips**: complete native executor (725), resume (50), GitHub broker (16), environment duplex-gate (4), and heartbeat instruction-copy (5) suites. The final run took 95.3s. Source hashes stayed unchanged through these checks. - The initial head passed 788 affected cases and full CI, but Apex scored it **3/5**. It found a dead HTTP/2 broker could remain reusable and a flag change could fail after retained instruction preparation. Both findings required the new repair. Three added before-fix regressions failed on channel availability, orderly close, and preparation identity. - The first repair run failed three new warm-instruction fixtures because fake provider close left its recorded state ready. The corrected fixture writes a suspended checkpoint without weakening the production ownership guard. A separate real-wire run then failed two availability assertions because its helper spread had frozen the readiness getter. The helper now preserves the getter. All failed attempts remain in the local evidence. - One disposable real Daytona sandbox exercised both transports on the initial transport patch through the production native broker and callback bridge. The provider shell wrapper used the then-current production declarations. During equal two-second idle windows, file mode issued **9 remote commands** and HTTP/2 issued **0**. Both variants rotated synthetic credentials between two run bindings and denied idle requests. Cleanup deleted the sandbox. - Daytona component setup took 3.64s for file mode and 4.74s for HTTP/2. This is an idle-traffic reduction, not a startup-speed claim. That component run predates the availability and preparation repair; it does not qualify the repaired paths on Daytona. The new failure controls use a real local HTTP/2 channel. The credential resolver was synthetic and no model was called. Real GitHub-account grants, staging replies, and the end-to-end latency target remain unverified by this component test. - Repaired-head CI found a policy-test false positive: a broad `secrets.` regex matched an upstream filename. The unchanged test reproduced the failure, and the corrected version passed all 15 controls. Master later supplied a stronger direct/bracket check, which we retained when resolving the merge conflict. Review then found that formatted expressions could stop that check at a quoted brace. The new reported-case baseline failed (one failed, 14 passed). The final check treats quoted strings as complete tokens, respects expression boundaries, and passes all 15 cases. The later master conflict also added `toJSON(secrets)` controls. The old branch pattern failed that whole-context case. Review subsequently found exponential backtracking with escaped apostrophes; the final forward scanner retains the quoted-boundary and whole-context controls and passes all 15 tests, including the bounded long-expression regression. No workflow or credential handling changed. All failed attempts remain recorded. - The full local repository test suite was not rerun. [Prior-head full CI](https://github.com/paperclipai/paperclip/actions/runs/38775801821), including `ci / verify` and `ci / e2e`, passed on previous head `e5f8bdb425081cf08110c0f7423f96609e405920`. All 52 check runs succeeded, two optional Storybook jobs skipped, and legacy Snyk passed. Completed Apex review is **5/5** on that exact head, with no unresolved review threads. ## Risks - Only an explicit server-selected opt-in can use HTTP/2. The existing provider-capability, channel-startup and authenticated file-fallback checks remain in the shared bridge. - Toggling the flag or losing the HTTP/2 channel replaces an idle retained provider at the next run boundary. Instruction preparation must make the same retirement decision. Normal ownership, settled history and cleanup checks remain in effect. - Lower idle traffic may reduce provider/controller pressure, but no staging latency improvement is claimed. This feature remains inactive when the existing flag is off. Other preparation and finalization costs remain. - No schema, API, UI, credential-selection, tracing endpoint or default-setting changes. ## Model Used - OpenAI Codex, based on GPT-6, with tool-assisted reasoning and code execution. The exact hosted snapshot ID and context-window size are not exposed in this session. ## 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>
14 KiB
GitHub identity during agent execution
Shared agents use the GitHub connection of the person whose accepted instructions they are executing. Task ownership remains unchanged. GitHub is optional: ordinary work can start without a connection; a private checkout, authenticated API call, or commit can fail when that operation needs credentials or author metadata.
Accepted instructions and continuations
run_identity_contexts records ordered revisions, stored message authors, originating causes, parent contexts, acceptance state, and redacted GitHub outcomes. heartbeat_runs.active_identity_context_id selects the current revision. Existing historical runs are not backfilled with inferred authorship.
Human messages use their stored authenticated author. Queued messages retain their delivery order. Accepting steering reserves a pending revision before delivery, then activates it after the provider acknowledgement. Rejected delivery leaves the prior revision active. An uncertain acknowledgement holds new credential acquisition; a later acknowledgement or its authenticated native event receipt reconciles the reservation. Replays cannot reactivate an older revision. Activation locks the task before the run, matching task and queue mutations so concurrent status changes cannot deadlock identity initialization.
Delegated work and interactions persist their originating context. Retries retain the originating run's active context. Background continuations carry their source run; dependency wakes use the task's continuation context, independently of its owner. Scheduled and webhook routines use the routine's responsible person; manual invocations use the caller, and edits preserve the routine's responsible person.
Managed GitHub operations
Executions with managed GitHub configured receive token-free git and gh launchers. Each launcher invocation resolves one eligible credential from the current run's accepted identity at operation start. A gh command's child Git processes inherit that command's captured identity; later steering does not change already-started operations.
Native runners retain a session-owned broker and launcher path across warm turns. The provider keeps an opaque transport token, not a GitHub credential or the previous run's signed capability. After acquiring exclusive session ownership, the controller binds the broker to the current company, agent, task, and run. Requests cannot choose another run or responsible person. The broker rejects requests while idle and discards credential responses if their run binding changed during acquisition. Each operation still rechecks the live run, accepted identity, grants, and trust policy through the shared credential resolver. Changing run IDs alone no longer replaces the provider process; changes to authentication mode, provider credentials, permissions, or other session configuration retain their existing retirement rules.
The session broker listens only on controller loopback and accepts only its authenticated GitHub credential operation. Remote executions reach it through the existing authenticated callback bridge. Its transport and launchers are retired with the provider session. If remote bridge startup fails, anonymous launchers keep ordinary work available; the next run retries setup with a fresh session. Controller restart/cold recovery still uses the existing checkpoint and process-recovery rules; the broker's in-memory authority is not persisted for adoption.
The native GitHub callback bridge uses HTTP/2 only when the server-selected
enableSandboxDuplexBridge flag and the sandbox's verified duplex capability
permit it. Missing settings, unsupported providers and failed channel startup
keep the authenticated file bridge. Changing the server flag retires a retained
warm bridge at the next normal run boundary, before a retained instruction copy
is claimed. A lost HTTP/2 channel also makes the broker unavailable and retires
that warm session at its next admission. An unchanged flag and a healthy broker
permit reuse. Normal session cleanup closes the channel as an orderly completion.
This selection changes transport only; operation-time credential checks remain
the same. Local and SSH execution retain their existing transport.
Other adapters continue using the run-scoped signed capability and public broker endpoint. That endpoint rejects browser origins and session cookies, validates a distinct signed runtime scope, and rechecks the company, agent, and live run. GitHub credentials are returned only to the managed command process, never persisted in identity history or injected into the long-lived provider process.
Low-trust executions cannot receive raw GitHub credentials, including dedicated agent tokens. The broker rechecks current agent, project, task, and retained run policies before credential resolution. An external guest's internal sponsor is accountable for the task, but does not authorize using the sponsor's account. Read-only access must use separately authorized tools that enforce that boundary.
Server-side Git operations and GitHub gateway calls follow the same selection rules. Approved gateway operations retain their signed originating identity. Connection audience and tool policies continue to apply to the selected person's connection. Native catalogs remain stable across identity changes, but each invocation resolves the selected grant again. Personal OAuth secret declarations survive connection pauses and metadata edits.
Managed commands disable ambient Git credential helpers, Git global/system configuration, host GitHub CLI configuration, and host SSH identity access. Per-operation GitHub CLI configuration is isolated in a writable configuration directory beneath the managed launcher directory. Missing credentials clear previous author and token values; no teammate, standing delegation, host token, or company-default user's account is substituted. Anonymous/local operations remain available where supported.
Without a managed identity, local commits can use an explicitly configured
repository identity or git -c user.name=... -c user.email=.... The launcher
leaves author/committer environment variables unset and requires configured
identity instead of guessing the host user's details. Managed shell profiles
remove empty identity overrides after environment merging, so agents do not
need to unset them per command. A captured managed identity still takes
precedence over repository configuration.
Remote launchers prepend their directory to the execution target's effective
PATH. An explicit remote PATH override is preserved; otherwise Paperclip
reads the provider's environment before staging the launcher shell files.
This keeps legacy NVM and user-local agent installations available alongside
newer images with system-wide CLIs. The generated shell files retain that
combined path with managed git and gh first. The launcher directory has
its own CommonJS package scope, so the extensionless Node launchers work
inside repositories that declare "type": "module" without changing the
project's package configuration. Sandbox command checks use
the same sanitized environment as execution, so a CLI visible only in the
provider's default environment cannot pass the launch check. Failed path
discovery stops startup instead of silently falling back to a minimal path.
Scripts that previously read a persistent GH_TOKEN must use managed git, gh, or GitHub gateway tools. Managed execution skips legacy GitHub token bindings in agent, environment, project, and routine configuration before secret preflight. Configure personal or dedicated access through the GitHub connection instead. Directly invoking an unmanaged executable or retaining a token obtained during an earlier invocation is outside the managed invocation contract.
Legacy hosts and networking
When no managed GitHub connection is installed for an agent, standard-trust local and SSH executions retain that execution host's existing Git and GitHub CLI credentials, configuration, credential helpers, and SSH agent. Paperclip does not import controller credentials into an SSH target. Sandbox, plugin, and low-trust executions do not receive this compatibility fallback. Once a managed connection is configured, unavailable or revoked access never falls back to host authentication. Switching modes replaces the provider process while preserving the settled conversation.
Runner network access is independent of GitHub credentials. The controller
enables networking for standard-trust execution. Low-trust runs and runners
without a controller network decision retain a restricted default. An operator
can set PAPERCLIP_RUNNER_NETWORK_ACCESS=disabled to restrict normal execution;
user environment bindings cannot override that decision. Outer execution-
environment network restrictions still apply. The controller projects the assigned worktree's Git metadata paths so
Git can operate without exposing unrelated workspace or provider state. The
sandbox also receives read access to validated provider executable resources
and the target host's DNS and CA files, including resolver symlink targets
outside /etc. Provider credential directories remain isolated.
A managed broker outage does not prevent local Git operations. Launchers clear credentials and run the command without authentication, with a redacted error category identifying configuration setup, transport, or capability rejection. They do not retain a previous operation's token or replay a GitHub operation.
Healthy eligible grants for the same stable GitHub account ID take precedence over duplicates with failed health checks. Credential acquisition can retry once against another grant for that same principal and account, before any GitHub operation begins. Run identity diagnostics include the selected connection and grant IDs, without credential values. Access-refresh conflicts retry once against current state and never turn a concurrency conflict into a reconnect requirement.
Dedicated accounts and diagnostics
An explicit dedicated-agent grant overrides personal selection. Revoked, disabled, unavailable, or ambiguous dedicated grants do not fall back to a person's account. Removing the dedicated configuration restores personal selection.
Connection setup and permissions display: “This agent uses this GitHub account for everyone's work, instead of the person giving instructions.”
The GitHub permissions page shows repositories across all connected accounts in one scrollable list. It has no account filter or repository search. Repository icons, private-repository indicators, refresh, and GitHub configuration links remain available. The “Add More Repos on GitHub” button opens GitHub’s app installation and repository-access setup.
Multiple eligible connections for the same GitHub account are treated as one identity, using GitHub's stable account ID rather than its login. The resolver selects an available grant, preferring the newest authorization with a stable ID tie-breaker. Duplicate eligibility includes an active credential record with the correct owner, the OAuth access-token reference, and repository access metadata. It keeps that grant's credential and connection policy together; it does not combine repository access or bypass connection audiences. Distinct accounts or unidentifiable duplicate grants remain ambiguous. Managed commands print the redacted reason when GitHub access is unavailable, while unrelated local operations can still proceed without credentials.
Run details show identity revisions and redacted GitHub results: responsible person, selected login when available, personal/dedicated source, and an unavailable reason. Tasks do not receive an additional identity indicator or takeover action.
Deployment and verification
Deploy the schema, server broker, launcher staging, and runtime environment contract together. Already-running processes retain their original environment; only newly dispatched processes receive the broker contract. Run-scoped capabilities remain valid only while their bound run is active.
Focused coverage lives in run-identity.test.ts, github-operation-credentials.test.ts, and github-launcher.test.ts, alongside the native steering, gateway, routine, and callback-bridge suites. Live acceptance additionally requires two authenticated Paperclip users, two authorized GitHub accounts, and a designated disposable repository for push verification. Local commit metadata and mocked API results do not replace that live push test.
Release procedure
- Back up the instance database using the normal deployment procedure.
- Build and deploy one revision containing migrations 0240–0245, the server broker, managed launchers, and the runner artifacts. Run the standard pending-migration check before admitting new runs. These additive migrations are safe to replay and do not infer authorship for historical runs.
- Let pre-rollout executions finish with their original runtime contract. New executions must have an active identity context and the managed launcher capability before provider startup.
- Check one ordinary run without a GitHub connection, then an authenticated GitHub operation. Inspect the run details for the responsible person and credential outcome. Verify a queued continuation on the same conversation.
- If rollback is needed, finish or explicitly stop executions using the new broker before removing its endpoint. Keep the additive schema and identity history. Do not drop identity columns or tables to roll back application code.
Remote acceptance uses the existing paid runner workflow with a narrow selection. Run it against the same immutable revision as the release; a successful local test does not qualify a different remote runner artifact.
Identity history survives deletion of the originating agent or run, so surviving subtasks and approvals retain their responsible person. The company foreign key and company-deletion service remove these company-scoped records when their company is deleted. Run-scoped adapters remove their managed launcher files at the terminal boundary. Native warm sessions retain their token-free launchers and inactive broker until session retirement; environment deletion and orderly controller shutdown close idle owners first. Cleanup failures are logged and do not change the run result.