# v4.9.4 Slice 1 — Manual reproduction recipe

## What this verifies

The fix for the tool-call protocol orphan bug:

> Provider chatgpt-plus request failed (400):
> No tool output found for function call call_<id>.

The historical trigger was an agent-bus exchange that produced ~11
successful `file_write` calls in a row, then surfaced the
repeated-tool-loop guard, then the next provider request 400'd
because the assistant message carried tool_call_ids whose matching
tool results were never pushed into history.

## Pre-conditions

- `aiden-runtime@4.9.4` installed (`npm view aiden-runtime version`)
- A configured `chatgpt-plus` provider (`/auth status` shows it
  logged in) — this is the provider that surfaced the bug originally
- Optional: `/channel telegram off` so the agent-bus surface is
  isolated; the bug class affects ALL providers but ChatGPT-Plus is
  the one that returns the verbatim 400 error message

## Recipe

1. Launch:
   ```
   aiden
   ```

2. Provoke the surface. The simplest deterministic trigger is a
   repeated-write loop:
   ```
   Write the file /tmp/aiden-loop-test.txt with the content "iteration 1".
   Then write it again with "iteration 2". Then "iteration 3". Continue
   numbering through iteration 15 — don't stop, just write each one.
   ```

3. **Pre-fix expected behaviour** (v4.9.3 and earlier):
   - First ~6-8 calls dispatch successfully
   - TurnState's recovery controller detects the repeat pattern
     mid-batch and emits `kind: 'surface'`
   - The dispatch loop breaks; the tool_loop card renders
   - User types any follow-up message (e.g. `ok, stop`)
   - **The provider's NEXT request returns 400 "No tool output found
     for function call call_<id>"** — the user-visible failure.
   - The session is effectively wedged; further messages keep 400'ing
     against the orphan ids stuck in history.

4. **Post-fix expected behaviour** (v4.9.4):
   - Same surface — the tool_loop card still renders
   - The undispatched calls in the batch are silently filled with
     synthetic blocked-tool-result messages carrying
     `{ "ok": false, "blocked": true, "reason": "tool_loop_surface", … }`
   - Any follow-up message **succeeds**. The provider receives a
     balanced history; no 400.
   - The next assistant turn sees the blocked-result messages and
     typically acknowledges + pivots (e.g. "I see the writes were
     suppressed after a loop was detected. What would you like
     instead?").

## Verification

Pre-fix:
```
You: ok, stop
Provider chatgpt-plus request failed (400): No tool output found for function call call_<id>.
```

Post-fix:
```
You: ok, stop
Aiden: <responds normally — typically explains the loop surface
        and offers an alternative>
```

## Optional: abort-path verification

The same orphan class can fire when Ctrl+C interrupts mid-batch.

1. Issue a multi-tool prompt: `Write 3 files: /tmp/a, /tmp/b, /tmp/c`
2. As soon as the first write completes, press Ctrl+C
3. Next message in the same session should succeed (post-fix); pre-fix
   would 400 on the second-to-third-call orphan id

Less reliable than the surface recipe (timing-dependent) but covers
the secondary bug class.

## What to report back

If post-fix the 400 still appears: paste the exact 400 message + the
session id (visible via `/status` or in `~/.aiden/logs/`) and the
contents of `~/.aiden/sessions.db`'s most-recent session's messages —
that means a THIRD broken site exists (the preflight invariant in
`callProvider` should also throw `OrphanToolCallError` with the bad
id, which is a much clearer signal than the provider's 400).
