Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(llm): sanitize x-understudy-tags values to Understudy's required format

merged#2781CedarCopilot

CedarCopilot wants to merge 0 commits into staging from fix/sherlock-sequencer-em-dash-signature-20260914-150416

Live on prod since Mon, 3 of 5 surfaces workingTimeline and evidence
  1. Opened
    Sep 14, 2026, 12:07 PM
  2. Merged
    Sep 14, 2026, 12:28 PM
  3. Live on prod
    Sep 14, 2026, 12:28 PM
  4. Observed 2 days, 5 surfaces, 178 requests
    Sep 14, 2026, 12:28 PM
  5. Pipelines steady after this deploy
    Sep 14, 2026, 12:28 PM
  6. Verified

    Live on prod since Mon, 3 of 5 surfaces working

    Sep 17, 2026, 11:30 AM

Behaviors Libra is checking

Before sending requests to Understudy, sanitize every x-understudy-tags value by lowercasing it, replacing disallowed characters with hyphens, removing leading hyphens, truncating it to 64 characters, and using "0" when the result is empty so Cedar identifiers cannot make the gateway reject the request.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Sep 17, 2026, 11:30 AM

Libra measured 3 of 5 production surfaces on prod. 2 surfaces had under 20 requests, so Libra has not judged them. Libra checks hourly for 3 days after each deploy.

What was wrong

Jacob Doty (Aspire) reported his "Zeke the Sequencer" outbound agent was still using em dashes and an inconsistent email signature despite already having explicit "no em dashes" and "sign off as Jacob" rules in its subagent doc.

Root cause traced via Sherlock (Postgres chat_messages for the Slack thread slack:T02511TSJ:C0C0XC2FAR4:1789410187.953389, 2026-09-14):

  1. Jacob asked the chat agent to build a sequence. The agent correctly called spawn-subagent(type="orchestrator", agentId="5a688ce6-c542-44ba-8a27-581317d3122a") , Zeke's real subagent.
  2. That call 400'd against the Understudy gateway:
    "x-understudy-tags key 'userId' must match /^[a-z0-9][a-z0-9-]{0,63}$/"
    
    orchestrator-agent.ts passes the raw Cedar userId straight into x-understudy-tags (WHJSsQgW9nP9xKHmjPkZtYgPwSbow2US , mixed case, no hyphens, per Better Auth's id format), which violates Understudy's required tag pattern. This breaks the call for every Cedar user, not just Jacob, whenever the cedar-understudy-orchestrator flag routes a spawn-subagent(type="orchestrator") call through the gateway.
  3. With Zeke's real invocation dead, the chat agent silently fell back to a bare run-code-executor call with a freehand task description , which doesn't inherit any parent subagent's prompt, so none of Zeke's style rules (no em dash, sign off as Jacob) were ever in scope.
  4. The resulting document had 55 em dashes and an inconsistent signature , not because the config was ignored, but because Zeke never ran.

What changed

understudyHeaders() (apps/server/src/services/llm/understudy.ts) now sanitizes every x-understudy-tags value , lowercases it, strips characters outside [a-z0-9-], strips leading hyphens, and truncates to 64 chars , before building the header. This is centralized so no future caller needs to separately learn Understudy's tag format.

Added test coverage in resolve-model.test.ts covering the exact mixed-case userId that broke this, plus an arbitrary tag with disallowed characters.

How to test

  • pnpm --filter @zero/server exec vitest run src/services/llm/__tests__/resolve-model.test.ts , 12/12 passing, including the two new sanitization cases.
  • pnpm --filter @zero/server run types , clean.
  • In prod: watch for spawn-subagent(type="orchestrator") calls in agent_tool_calls for users with cedar-understudy-orchestrator enabled , they should no longer show a TOOL_EXECUTION_FAILED / x-understudy-tags 400 in the result, and should not be followed by a run-code-executor fallback for the same task.

Separately (not in this PR , a live document edit, not code): Jacob's two sequencer subagent docs (zeke-the-sequencer.md, event-outreach-sequencer.md) were patched via writeDocumentGuarded to change the sign-off rule from "Jacob" (no dash) to "-Jacob" per his request, and verified via the playbook health + Yjs sync checks.

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01UPnwxwkTP5oh7k6MhZn97b

Greptile Summary

This PR centralizes normalization of Understudy tag values so Cedar identifiers satisfy the gateway’s lowercase, character, leading-character, and length constraints.

  • Sanitizes every tag value before serializing x-understudy-tags.
  • Preserves environment tagging while preventing invalid values from rejecting requests.
  • Adds regression coverage for the reported mixed-case user ID and disallowed punctuation.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete correctness, security, or repository-rule violations identified.

The sanitizer handles the documented character, first-character, empty-value, and maximum-length requirements, and the regression tests exercise the production failure that motivated the change.

Important Files Changed

FilenameOverview
apps/server/src/services/llm/understudy.
Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
model_chunk On-Event Execution Agent (Understudy)31 β†’ 1020 β†’ 0 (0%)1 ms β†’ 1 ms0Working
102 requests since the deploy with 0 errors (0 errors in the 31-request baseline before it). p95 1 ms, was 1 ms.
model_step On-Event Execution Agent (Understudy)10 β†’ 300 β†’ 0 (0%)23743 ms β†’ 28865 ms0Working
30 requests since the deploy with 0 errors (0 errors in the 10-request baseline before it). p95 28865 ms, was 23743 ms.
model_chunk Automation Agent (Understudy)12 β†’ 280 β†’ 0 (0%)1 ms β†’ 2 ms0Working
28 requests since the deploy with 0 errors (0 errors in the 12-request baseline before it). p95 2 ms, was 1 ms.
model_chunk On-Event Orchestrator Agent (Meeting, Understudy)4 β†’ 100 β†’ 0 (0%)1 ms β†’ 1 ms0Insufficient traffic
10 requests, under the 20 Libra needs
model_step Automation Agent (Understudy)4 β†’ 80 β†’ 0 (0%)33865 ms β†’ 848145 ms0Insufficient traffic
8 requests, under the 20 Libra needs

Changed files β†’ surfaces

  • apps/server/src/services/llm/understudy.tsmodel_chunk On-Event Execution Agent (Understudy)model_step On-Event Execution Agent (Understudy)model_chunk Automation Agent (Understudy)model_chunk On-Event Orchestrator Agent (Meeting, Understudy)model_step Automation Agent (Understudy)
  • apps/server/.claude/skills/sherlock/SHERLOCK_SYSTEM_KNOWLEDGE.mdno production surface mapped
  • apps/server/src/services/llm/__tests__/resolve-model.test.tsno production surface mapped