mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-21 21:31:12 +02:00
A sync-tool batch runs under Promise.all, which rejects the moment one tool's result append fails (a transient fs fault, or errorMessage() throwing on a nullish thrown value). That rejection propagated out through withLock and freed the per-turn lock while sibling tools were still executing. A straggler's later append then ran with no lock held and validated against a stale in-memory history rather than the file — so if the caller re-advanced in between (writing the recovery indeterminate result for the not-yet-settled tool), the straggler appended a second tool_result for the same call. Duplicate results make reduceTurn throw on every future read, permanently corrupting the turn file and, through context references, every session behind it. - executeAllowedTools now awaits Promise.allSettled and rethrows the first fault only after every tool has finished appending, so a fault never orphans a sibling. - advance() drains the append queue (settleAppends) before withLock releases the turn, closing the fire-and-forget reportProgress path and any future append-path rejection. - errorMessage() uses optional chaining so a `throw null`/`throw undefined` yields a normal isError tool result instead of a TypeError that escapes as an infrastructure rejection (the same fault that triggers the lock-escape mid-batch). Adds two regression tests (both fail without the fix): a nullish throw records an isError result and completes the turn; a faulted batch does not settle until the slow sibling's append is durable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| core | ||
| shared | ||