{
 "model": "claude-haiku-5-5",
 "category": "background",
 "scenarios": [
  {
   "id": "d1-two-servers",
   "status": "pass",
   "product_defects": [],
   "task_outcome": "Started python http.server on 8810 (bg-a098d215) and 8811 (bg-eb2035c3); both returned HTTP 200 with curl. TaskStop on the first gave exit 143, after which 8810 refused (curl exit 7) and 8811 still returned 200. TaskOutput reported 'stopped, exit code 143' and 'running'. I checked: exit code [0]; 7 tool-use/result pairs match in the log and the saved wire; tasks frames move running to stopped; background-tasks.jsonl marks bg-eb2035c3 stopped with endedWithProcess; result.json has no leftover listeners on 8810-8814; no process remains now; my cost recompute of $0.001844635 over 6 requests matches.",
   "notes": "Cache-read ratio 0.96. 31 tools, including TeamMemoryWrite and the 6-skill Skill catalog; no AskUserQuestion, mcp__* or DeferredToolCall. No learned patterns file (no verification commands ran), which is expected. The model said correctly that the second server stays running until the session closes."
  },
  {
   "id": "d2-bad-bg-command",
   "status": "pass",
   "product_defects": [],
   "task_outcome": "The background `definitely-not-a-command` failed at once: state failed, exit 127, stderr 'command not found' in the result and the tasks frame. `sh -c ... exit 3` printed starting/crashed, failed with exit 3, and TaskOutput reported 'failed, exit code 3' with both lines. I checked: tasks frames show failed with exitCode 127 and 3; toolHistory states are failed (calls that ran and exited non-zero); nothing hung; exit [0]; pairs match; cost $0.001090875 matches.",
   "notes": "The exit code appears three times in the model-facing result. This is the known by-design behaviour and reads correctly. The model's report is accurate."
  },
  {
   "id": "d3-unicode-bg",
   "status": "pass",
   "product_defects": [],
   "task_outcome": "The Python progress script ran to completion (exit 0). Every tasks-frame tail, the Bash 'Output so far' (lines 1-4 while running) and TaskOutput (all 6 lines) carry intact 进度/✅/绿色/🚀/👩‍💻 (ZWJ sequence included) and raw ESC[32m/ESC[0m. There is no U+FFFD in any log or session. outputBytes grows by exactly 54 bytes per line, so no multi-byte character is broken at a chunk edge. Exit [0]; pairs match; cost $0.001232365 matches.",
   "notes": "The model called TaskOutput only once, after the script ended. It used the launch result's 'Output so far' as the read while running, and said so in its reply. That is a model choice. In its own reply the model typed the traditional character 進 on line 1, then corrected itself; the tool output was correct (进). There was no 'exited while you worked' notice, because TaskOutput had already seen the exit (reported:true), which is correct. ANSI escapes pass through unchanged, which is sane."
  },
  {
   "id": "d4-watcher-left-running",
   "status": "pass",
   "product_defects": [],
   "task_outcome": "Started `LISTEN_ON=8825 node --watch server.mjs` (bg-076d2c9b); curl returned 'ok'. FileEdit changed the reply to 'updated', and curl then returned 'updated'. TaskOutput showed 'Change detected... Restarting'. The watcher was left running as asked. I checked: server.mjs now ends with \"updated\"; after the driver exited, background-tasks.jsonl records the task as stopped with endedWithProcess; result.json has no listener on 8825-8829 and no processes; no node --watch or gcode-watchdog process exists now. Exit [0]; pairs match; cost $0.00162467 matches.",
   "notes": "The process-group cleanup on CLI exit covered node --watch's child. The runner's process grep matches only command lines that contain the run dir, so the port check and my ps scan are the stronger evidence here."
  },
  {
   "id": "d5-max-bg",
   "status": "pass",
   "product_defects": [],
   "task_outcome": "Calls 1-8 started (bg-919cef78 … bg-9d44c21b). Call #9 failed with 'Error: 8 background tasks are already running. Stop one with TaskStop first.' and its toolHistory state is failed. Call #10 (same command, only the description differs) was refused by the repeat guard ('Not run: this exact Bash call failed…') and settled as refused. TaskList listed all 8 shells as running. Eight TaskStops each gave exit 143. A final TaskList and the tasks frame show all 8 as 'stopped, exit code 143'. Exit [0]; saved pairs 20/20; cost $0.002879455 matches; no leftovers.",
   "notes": "The refused #10 has a tool-result frame with no tool-use frame, which is by design. Treating #10 as the same call as #9 is intended: failureBudget ignores the Bash description. The model chose not to free a slot to retry #9/#10 and explained why; that is a reasonable choice. I cannot observe sleep processes after the fact, because they would have ended at 300 s anyway. The evidence that none was left is the exit-143 TaskStop results, the stopped states and an empty ps check now."
  },
  {
   "id": "d6-interrupt-resume",
   "status": "pass",
   "product_defects": [],
   "task_outcome": "Turn 1: npm init plus express 5.3.0, wrote index.js with /health, started the server in the background, and curl returned {\"ok\":true}. The stop came during TaskStop. TaskStop settled as cancelled with a single message: 'The turn was stopped before background task bg-84f28e32 ... ended. It was sent SIGTERM, and gets SIGKILL 2 s later'. The parallel FileRead got 'Not run: the turn was stopped before this FileRead call started'. The error frame reads 'Stopped before this turn finished.' The saved turn has synthetic results plus the 'user stopped this turn' marker, and background-tasks.jsonl marks bg-84f28e32 stopped with endedWithProcess. Turn 2 (resume, same session, no 400) saw the 'background tasks ended' context. It checked that the port was free, re-read index.js, did not repeat npm init or the write, added /time, restarted the server, verified both routes, and stopped it (exit 143). I ran the app myself: /health and /time both respond, and I stopped it. Exit codes [0,0]; pairs 14/14; cost $0.00375974 over 13 requests matches; no leftovers.",
   "notes": "No 'The operation was aborted.' text anywhere. The model's summary wrongly says 'The interrupted turn had left the first server running'. It had in fact found the port free; this is a model wording slip, not a G-Code problem. npm debug logs in the run HOME come from npm itself."
  },
  {
   "id": "d7-sigkill-orphan",
   "status": "pass",
   "product_defects": [],
   "task_outcome": "The node server (bg-b18c2513, pgid 51650) started on 8840 and curl returned 'ok'. During the foreground `sleep 200` the runner sent SIGKILL to the CLI: result.json shows sigkill killed:true and serverGoneMs 713, which fits the 1-s watchdog poll in utils/watchdog.ts. The driver exit code is 137 (exitedEarly), as designed. The saved session holds a synthetic result for the sleep ('No result was recorded: the process ended before this call finished…') and the 'process ended before this turn finished' marker, with pairs 3/3. Cost $0.000812345 matches; result.json has no leftovers; no cli.ts, watchdog or node process remains now.",
   "notes": "A stale .lock (pid 51644, now dead) and a 'running' record in background-tasks.jsonl remain. Both are unavoidable after SIGKILL, and store.ts treats a lock whose owner is dead as stale. The scenario text mentions snapshot3; the runner matches the snapshot10w CLI path, which is correct."
  }
 ]
}