## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - When an agent run fails, Paperclip sends a redacted failure report to Sentry so the team can fix the cause > - Redaction treats the server's own environment values as possible secrets and removes them wherever they appear in the report > - Since #19461, every value under an environment name the allowlist does not know counts as a secret, even very short ones such as `vi`, `RUN`, `off` or `1` > - On a host with such settings, the report text is damaged: `service` becomes `ser***REDACTED***ce`, and a `1` anywhere can vanish. One server test failed on every such host, while CI (which has no such values) stayed green > - This pull request keeps the fail-closed rule for an agent's configured environment, and requires a plausible credential in the server's own environment > - The benefit is that Sentry reports stay readable on every host, and real credentials stay private ## Linked Issues or Issue Description Refs #19461 (introduced the catch-all), #14585 (added the failing test). **What happened?** `heartbeat-process-recovery.test.ts` › "redacts opaque environment-bound credentials from Sentry diagnostics: result" failed locally on `master`: `expected 'ser***REDACTED***ce' to be 'service'`. Instrumentation showed that the server's own `process.env` contributed these redaction values: `["false","67591","true","0x0","RUN","off","vi","64","1","0","8"]`. They came from a desktop agent session's environment. **Expected behavior** Short host settings are not credentials. Declared, secret-named and credential-shaped values stay private. An agent's configured environment keeps its fail-closed rule. **Steps to reproduce** Run that test on a machine whose environment contains `SOME_UNKNOWN_NAME=vi`. ## What Changed - `packages/adapter-utils/src/command-redaction.ts`: `collectDiagnosticEnvironmentSecrets` gets an option, `{ hostProcess: true }`. With it, an undeclared value under an unrecognized name is collected only in these cases: - it has at least 8 characters and is not a bare number, hex number or boolean-like word; or - its name has a credential segment: `TOKEN`, `SECRET`, `PASSWORD`, `BEARER` or `PRIVATE`, or a final `KEY` or `KEY_ID`. Without the option, behavior is unchanged. - `server/src/services/run-failure-report.ts`: the report collects the server's own `process.env` with `hostProcess: true`. The agent's configured environment (`heartbeat.ts`) and the provider environment (`terminal-session-failure.ts`) keep the default. ## Verification - `command-redaction.test.ts` (42 pass). The new test fails on `master` and passes here. It checks three things: the host's short values are not collected; a short value under `AWS_ACCESS_KEY_ID` and a long opaque value stay private; and an agent binding `CUSTOM_BINDING: "1"` stays private. - `run-failure-diagnostics.test.ts` (135 pass, unchanged). These tests pin the agent-environment fail-closed rule. - `heartbeat-process-recovery.test.ts` › "redacts opaque environment-bound credentials…": both modes pass here. The `result` mode failed on unchanged `master` on this host. - The adapter-utils and server typechecks pass. ## Risks - The server's own environment now leaves out a non-credential-named value that is shorter than 8 characters or is a bare number or boolean, for example `MY_PIN=1234`. Values under credential names, declared secrets and credential-shaped values are still redacted. - No Telemetry path changes; this is the Sentry run-failure report. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code execution in Claude Code. ## 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 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@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) toremoteDirbefore the run starts. Runs with nogit remoteconfigured.restoreWorkspaceFromSshExecution({ spec, localDir, remoteDir, ... })— syncs the remote cwd back intolocalDirafter the run, including any new commits the agent created. Also runs with nogit remoteconfigured.
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.