mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-13 07:12:00 +01:00
test(chaos): W2C changes the agent configuration the way the settings form does (#21011)
## 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 <noreply@anthropic.com>
This commit is contained in:
1 parent
ad2d123dad
commit
0cfab73520
3 files changed
+6
-4
No files matched your search
@@ -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.
|
||||
|
||||
|
||||
@@ -188,7 +188,8 @@ export async function rewakeStep(input: RewakeStepInput): Promise<void> {
|
||||
const idleTimeoutMs = rewakeIdleTimeoutMs(plan.index);
|
||||
const change: NonNullable<RewakeRecord["configChange"]> = { 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<string, unknown> | 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);
|
||||
|
||||
@@ -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<T>(route: string) { return (route.endsWith("/interactions") ? interactions : []) as T; },
|
||||
async get<T>(route: string) { return (route.endsWith("/interactions") ? interactions : route === "/api/agents/a1" ? { adapterConfig: { model: "m1" } } : []) as T; },
|
||||
async post<T>(route: string, body?: unknown) { calls.push({ method: "post", route, body }); return { id: "c1" } as T; },
|
||||
async patch<T>(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);
|
||||
|
||||
Reference in new issue
Block a user