From 0cfab7352007b0a9d953b53ab3dd3e25e3a03318 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Thu, 12 Nov 2026 05:07:50 -0600 Subject: [PATCH] test(chaos): W2C changes the agent configuration the way the settings form does (#21011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The restart chaos re-wake cell W2C changes the agent's configuration after a run succeeds and before the task is woken again, so the next run cannot resume the saved session > - The harness sent a partial `adapterConfig` patch (`{ idleTimeoutMs }`). On a volume-free server that patch is refused for the Codex agent with 422 "Volume-free servers require managed agent instructions", so the configuration never changed and W2C cells only tested W2 > - The agent settings form does not send a partial patch: `buildAgentUpdatePatch` (`ui/src/lib/agent-config-patch.ts`) sends the whole merged `adapterConfig` with `replaceAdapterConfig: true` > - This pull request makes W2C change the configuration the same way the settings form does > - The benefit is that the W2C cell exercises the configuration change on every provider while the partial-patch refusal is handled separately ## Linked Issues or Issue Description **What is the problem?** W2C's configuration change is refused with 422 for a Codex agent on a volume-free server, so the cell does not test what it is named for. **What is the expected behavior?** W2C changes `idleTimeoutMs` the way a person would in the agent settings form, and the change succeeds. **Steps to reproduce** Run a Daytona wave with `--ephemeral-server --adapters codex --workloads W2C --faults K0`. The timeline records `rewake_config_changed` with `httpStatus: 422` and the check `config_changed_before_wake` fails. ## What Changed - `tests/restart-chaos/rewake.ts`: W2C reads the agent and PATCHes `{ adapterConfig: { ...existing, idleTimeoutMs }, replaceAdapterConfig: true }`. - `tests/restart-chaos/shape-cells.test.ts`: the W2C step test expects the full-config patch. - `tests/restart-chaos/README.md`: W2C describes the patch it sends. ## Verification - `pnpm exec vitest run --config tests/restart-chaos/vitest.config.ts tests/restart-chaos/shape-cells.test.ts` passes. - `node scripts/typecheck-test-projects.mjs tests/restart-chaos` passes. - A volume-free Codex W2C wave is queued to confirm the change returns 200 live. ## Risks - Low risk. Test harness only. The partial-patch 422 itself is a separate product question and is not changed here. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`) in Claude Code, with tool use and code execution. ## 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 --- tests/restart-chaos/README.md | 3 ++- tests/restart-chaos/rewake.ts | 3 ++- tests/restart-chaos/shape-cells.test.ts | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/restart-chaos/README.md b/tests/restart-chaos/README.md index 2e015e6a7c2..f32ca0d93ca 100644 --- a/tests/restart-chaos/README.md +++ b/tests/restart-chaos/README.md @@ -1150,7 +1150,8 @@ seeded 0–60 s after that run settled (`rewake.ts`): scheduler's own `issue_monitor_due` wake starts the run. - **W2Q**: phase 1 asks one `request_human_input` question and parks. A board comment (a note to include, not the answer) wakes the task while the question is pending. The board answers after that run settled. -- **W2C**: as W2, and first the board changes the agent's `adapterConfig.idleTimeoutMs`, so the next run +- **W2C**: as W2, and first the board changes the agent's `adapterConfig.idleTimeoutMs` (sending the whole + `adapterConfig` with `replaceAdapterConfig: true`, as the agent settings form does), so the next run cannot resume the saved session. SR1 (`session-faults.ts`, #20844) is the same change while a question is parked; W2C is the change after a run succeeded. diff --git a/tests/restart-chaos/rewake.ts b/tests/restart-chaos/rewake.ts index 369d433ba56..a4d6430e0f0 100644 --- a/tests/restart-chaos/rewake.ts +++ b/tests/restart-chaos/rewake.ts @@ -188,7 +188,8 @@ export async function rewakeStep(input: RewakeStepInput): Promise { const idleTimeoutMs = rewakeIdleTimeoutMs(plan.index); const change: NonNullable = { at: now, idleTimeoutMs, httpStatus: null, error: null }; try { - await input.api.patch(`/api/agents/${input.agentId}`, { adapterConfig: { idleTimeoutMs } }); + const agent = await input.api.get<{ adapterConfig?: Record | null }>(`/api/agents/${input.agentId}`); + await input.api.patch(`/api/agents/${input.agentId}`, { adapterConfig: { ...(agent.adapterConfig ?? {}), idleTimeoutMs }, replaceAdapterConfig: true }); change.httpStatus = 200; } catch (error) { change.httpStatus = statusOf(error); diff --git a/tests/restart-chaos/shape-cells.test.ts b/tests/restart-chaos/shape-cells.test.ts index 9700241cc3f..ab4a08ea1d5 100644 --- a/tests/restart-chaos/shape-cells.test.ts +++ b/tests/restart-chaos/shape-cells.test.ts @@ -51,7 +51,7 @@ describe("W2, W2Q, W2C: re-wake after success", () => { const calls: Array<{ method: string; route: string; body: unknown }> = []; const record = { plan: { taskId: "t01", index: 3, nonce: "0bcd1204", followUpAfterMs: 10_000, workload: "W2", ...plan } } as unknown as TaskRecord; const api = { - async get(route: string) { return (route.endsWith("/interactions") ? interactions : []) as T; }, + async get(route: string) { return (route.endsWith("/interactions") ? interactions : route === "/api/agents/a1" ? { adapterConfig: { model: "m1" } } : []) as T; }, async post(route: string, body?: unknown) { calls.push({ method: "post", route, body }); return { id: "c1" } as T; }, async patch(route: string, body?: unknown) { calls.push({ method: "patch", route, body }); return {} as T; }, }; @@ -94,7 +94,7 @@ describe("W2, W2Q, W2C: re-wake after success", () => { it("W2C: changes the agent configuration first, then wakes at least 5 s later", async () => { const { calls, record, step } = harness({ workload: "W2C", followUpAfterMs: 0 }); await step(sample("in_review", [run("r1", "succeeded", 1_000)]), 5_000); - expect(calls).toEqual([{ method: "patch", route: "/api/agents/a1", body: { adapterConfig: { idleTimeoutMs: 301_003 } } }]); + expect(calls).toEqual([{ method: "patch", route: "/api/agents/a1", body: { adapterConfig: { model: "m1", idleTimeoutMs: 301_003 }, replaceAdapterConfig: true } }]); await step(sample("in_review", [run("r1", "succeeded", 1_000)]), 7_000); expect(calls).toHaveLength(1); await step(sample("in_review", [run("r1", "succeeded", 1_000)]), 10_000);