Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(chat): stop V2 chat from reaching for Bash to edit existing Cedar docs

merged#2887CedarCopilot

CedarCopilot wants to merge 2 commits into staging from fix/chat-doc-edit-bash-detour

What Libra verified

  • Whole-document rewrites use upsert instead of full-document patching

    Libra ran this behavior against the change and confirmed the check detects when it breaks.

    Technical evidence
    • the harness guidance selects upsert with complete replacement content for whole-document rewrites and reserves patch for small localized changes

    Changed code: documentTool.schemas.ts.

Live on prod, watching, 2 days leftTimeline and evidence
  1. Opened
    Sep 22, 2026, 5:18 PM
  2. Sep 22, 2026, 6:29 PM
  3. Merged
    Sep 22, 2026, 6:49 PM
  4. Live on prod
    Sep 22, 2026, 6:49 PM
  5. Observed 1 hour
    Sep 22, 2026, 6:49 PM
  6. Watching

    Live on prod, watching, 2 days left

    Sep 22, 2026, 6:49 PM
  7. Pipelines steady after this deploy
    Sep 22, 2026, 6:49 PM

Behaviors Libra is checking

When V2 chat edits an existing Cedar markdown document, playbook, or note, it uses write-document directly instead of reading, reconstructing, diffing, or writing scratch files with Bash or other native filesystem tools.Not checked
prod
When a Cedar document requires a full rewrite, restructure, or deduplication, the agent selects write-document with mode=upsert and supplies the complete replacement content, while reserving patch for small localized edits.Not checked
prod

Libra has verdicts on 0 of 2 tracked behaviors on prod; 2 are still being checked. Libra checks hourly for 3 days after each deploy.

Bug

Zach Moskow (Concentrate) hung a chat turn twice today (2026-09-22) trying to dedupe his ~57KB email-style.md. Root cause, from the actual persisted tool-call log:

  1. The V2 chat harness (run-chat-agent-sdk.ts) read the document into Bash/Read/Write scratch files (FULL-OLD-CONTENT.md, copy1-content.md, ...) to reconstruct and diff it.
  2. It then called write-document's patch mode with old_string/new_string spanning nearly the entire document , holding two full copies of it in context.
  3. This tripped the Claude Code SDK's own "autocompact is thrashing" guard, which hard-aborted the run (Agent Interrupted, twice).

The harness's own system prompt is why: <skill_and_subagent_usage> (surface 'harness') says "for HTML, CSS, JSON, or any code artifact: generate it with Bash/Write... then call write-document" , a real, narrow rule for code artifacts that the model was generalizing to markdown resource docs, which it was never meant to cover.

Fix

  • chat-agent.ts (surface 'harness' only , the surface with real Bash/file tools; 'mastra'/'mastra-families' route file ops through run-code-executor/delegate(action="code") instead, a different mechanism not covered by this investigation): one added line telling the model that editing an existing Cedar document is a write-document job, not a Bash job.
  • writeDocumentTool.ts + documentTool.schemas.ts (the sibling document-write family tool carries the identical stale mode description and the identical latent risk on the master-families surface , not implicated in this incident, since Zach's session used write-document not document-write, but fixed for consistency rather than left as a known-live gap): reworded mode's description to explicitly steer upsert for a whole-document rewrite and warn against a patch spanning the whole document.

Validation

Built two eval scripts (apps/server/.claude/skills/playbook-instruction-eval/) reproducing the real toolset + real system-prompt text against a synthetic doc shaped like Zach's (whole document duplicated, scaled to 17KB):

VariantBash-detour rate (6 trials)
Baseline (today's prod prompt)6/6
Reworded mode description alone5/6 , confirms this alone is not the fix
System-prompt carve-out alone2/6
Carve-out + reworded mode together (this PR)0/6

First pass (write-document-mode-eval.ts) tested the mode reword in isolation with only write-document/read-document offered and got a null result (8/8 correct on both variants) , it wasn't giving the model the actual competing option (Bash) the real harness grants. write-document-bash-competition-eval.ts is the real test, kept alongside it as the record of why the first attempt wasn't sufficient.

Out of scope

Zach's actual duplicated email-style.md was already fixed directly against prod (surgical patch via writeDocumentGuarded, verified against Yjs sync + a fresh read-back) , a data fix, not a code change, so not part of this PR.

Test plan

  • pnpm --filter @zero/server run types , clean
  • Eval fire-rates above, run against live claude-sonnet-5
  • Confirm in staging that a large existing doc edit goes straight to write-document without a Bash detour

šŸ¤– Generated with Claude Code

RetriggerConfidence Score: 4/5

The production prompt and schema changes appear safe, but the explicit rep

Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/mail/tests/modules/chat-store/chatHistoryPopover.test.tsxno production surface mapped
  • apps/server/.claude/skills/playbook-instruction-eval/write-document-bash-competition-eval.tsno production surface mapped
  • apps/server/.claude/skills/playbook-instruction-eval/write-document-mode-eval.tsno production surface mapped
  • apps/server/src/mastra/agents/chat-agent.tsno production surface mapped
  • apps/server/src/mastra/tools/document/documentTool.schemas.tsno production surface mapped
  • apps/server/src/mastra/tools/document/writeDocumentTool.tsno production surface mapped