mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 07:10:34 +01:00
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The Paperclip runner keeps a saved Codex thread so the next run of a
task continues the same conversation
> - On each new run, the controller sends `run.attach` with the current
Codex launch arguments, and the runner refuses any change to "protected"
arguments
> - #19516 removed `--disable image_generation` from the Codex launch
arguments, but every thread saved before that deploy still records the
flag
> - The runner treated the feature toggle as protected, so every
pre-deploy Codex thread failed with `run.attach cannot change protected
launch arguments`
> - Same-run recovery then tried to replace the thread, the replacement
failed, and the tasks became blocked for an operator
> - This pull request treats Codex feature toggles as controller launch
policy, so a saved thread adopts the current toggles and restarts on the
same thread
> - The benefit is that a release that turns a Codex feature on or off
no longer strands every saved Codex thread
## Linked Issues or Issue Description
Refs #19516
**What happened?**
After a staging deploy that included #19516, Codex agent runs on tasks
with a saved thread failed at the start. The run log showed
`native_session_recovery_failed: PRP command run.attach failed:
run.attach cannot change protected launch arguments` (error code
`native_session_interrupted`). Same-run retries then failed with
`runner_continuity_break_archive_failed` and
`runner_remote_state_unavailable`, and the tasks were blocked with
"Workspace repair required". Four tasks on one Codex agent showed this
first failure. Each one last ran successfully before the deploy.
**Steps to reproduce**
1. Run a Codex task so that its thread is saved with the launch
arguments from before #19516 (`--disable image_generation`).
2. Deploy a revision that includes #19516.
3. Wake the same task again. `run.attach` sends the current arguments
without the toggle, and the runner rejects them.
**Expected behavior**
The saved thread continues under the current launch policy.
**Root cause**
`CodexCommandExecutor::stable_launch_args` drops only the per-run
filesystem and shell-environment entries before it compares saved and
current arguments. All other arguments, including Codex
`--enable`/`--disable` feature toggles, must be identical. The
controller computes the toggles from Paperclip policy for every launch,
so any change to that policy made every saved thread impossible to
attach.
## What Changed
- `provider_backend.rs`: `stable_launch_args` also skips `--enable
<feature>` and `--disable <feature>` pairs and the
`--enable=<feature>`/`--disable=<feature>` form. The function contract
comment now lists the rebindable arguments. `run.attach` then adopts the
controller's current toggles. Because the arguments changed, the
existing path stops the provider and restarts it on the same thread.
- New test
`saved_thread_adopts_the_controllers_current_codex_feature_policy`:
- A thread saved with `--disable image_generation` attaches with the
current arguments, keeps its thread ID, and records the new arguments.
- A new `--disable` toggle (a tighter policy) is also adopted.
- A new `--enable` toggle is adopted in both flag forms, and the thread
ID stays the same.
- A sandbox bypass flag, a different `default_permissions` profile, and
a different subcommand are still rejected with "protected launch
arguments", and the saved state does not change.
## Verification
- Red on `master` (8621bcf53e) with only the new test added:
```
test
provider_backend::tests::saved_thread_adopts_the_controllers_current_codex_feature_policy
... FAILED
a saved thread must follow the current feature policy:
DurableRunnerError("run.attach cannot change protected launch
arguments", None, None)
```
- Integration regression
`tests/codex_provider.rs::saved_codex_thread_attaches_after_a_release_changes_its_feature_toggles`
drives the real durable executor with the fake Codex app-server. It
saves a thread with `--disable image_generation`, attaches with the
current arguments, runs a second turn, and checks that the provider
restarted once. A sandbox-bypass change is still rejected. Red on
unfixed source:
```
the saved thread adopts the current feature toggles:
DurableRunnerError("run.attach cannot change protected launch
arguments", None, None)
```
Green with the fix. The full `codex_provider` binary passes (116
passed).
- Green after the fix (unit test):
```
test
provider_backend::tests::saved_thread_adopts_the_controllers_current_codex_feature_policy
... ok
```
- `cargo test -p paperclip-runner-core --lib`: 560 passed, 0 failed.
- `cargo test -p paperclip-runner-core --no-fail-fast`: all Codex,
native-provider-backend, and durable-recovery integration binaries pass.
Some tests in `acpx_park_readiness` and `stdio_mcp` failed. Different
tests in these same binaries also fail on unmodified `master` on this
machine. They are timing tests under load and do not use this code.
- Not run: a live staging attach with a pre-#19516 thread. The runner
binary in a sandbox must match the controller's binary digest, so this
fix reaches sandboxes with the server deploy.
## Risks
- A saved thread now accepts any `--enable`/`--disable` toggle that the
controller sends. The controller is the only source of `run.attach`, and
a new thread gets the same toggles. A tightened policy now also applies
to saved threads, where before the saved thread was stranded.
- The sandbox, permission profile, approval, model, and subcommand
checks do not change.
- When the toggles change, the warm provider process restarts on the
same thread once. This is the existing path for changed runtime
arguments.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edit, browser reads of staging).
## 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
- [x] All Paperclip CI gates are green
- [x] 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>