diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index a0d3bc38..e7e21c1d 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -2568,8 +2568,10 @@ describe("ClaudeAdapterLive", () => { index: 1, delta: { type: "input_json_delta", + // The model sometimes mis-closes the subject tag and the harness + // folds the next parameters into it; the plan step must stay clean. partial_json: - '{"subject":"Investigate flaky login test","description":"Find the race in the login spec"}', + '{"subject":"Investigate flaky login test\\nFind the race in the login spec\\nInvestigating","description":"Find the race in the login spec"}', }, }, } as unknown as SDKMessage); diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 541304d7..0c5af7db 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -1180,6 +1180,18 @@ function nonEmptyString(value: unknown): string | undefined { return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined; } +/** When the model mis-closes the subject parameter of a task tool call + * (`` instead of ``), Claude Code's lenient tool-call + * parser folds the following parameters into the subject string, so the whole + * description lands in the plan step. Cut a stray closing tag that is followed + * by another parameter or by the end of the string. */ +const LEAKED_PARAMETER_TAIL_PATTERN = /<\/[\w-]+>\s*(?:; - -/** The glyph that sits on the activity spine for one row. The accent halo is - * reserved for the turn's working row below the timeline tail; a still-running - * step gets a small accent tick, settled steps are quiet solid dots, - * warnings/errors are compact tone dots, and a collapsed group of steps is a - * hollow ring (same family, reads as "openable"). */ -function SpineNode({ kind }: { kind: SpineNodeKind }) { - if (kind === "running") { - return ( -