Persist queued delegations, and scrub secrets from what the harness stores - #196
Conversation
…tores 2.3 — durable delegations. The per-thread handoff queue lived in a Map and died with the process: a delegation queued right before a restart never ran, silently. It is now written to ~/.openmausbot/delegations.json on queue, drain, and discard, loaded at boot, and drained through the same path a settled turn uses — target and approvePeerComms are still re-checked at drain time; a source bot that no longer exists is skipped. Provider permissions still die with the process (nobody can answer for an unattended bot); queued work is not a permission. 2.4 — secret redaction, rescoped. The harness never stores tool RESULTS — CLI drivers run tools inside the CLI and the harness only sees names and outcomes — so there was no tool-result body to redact. What it does store, and now replays into every rebuild, is the bot's reply text, activity chip titles (an ACP engine's title can be the whole command line), and permission card summaries (the command being approved). Those can carry keys. - redactSecretsInText: high-precision content patterns only — known key prefixes (sk-, ghp_/github_pat_, xox*-, AKIA, AIza, npm_), JWTs, PEM private-key blocks, Bearer tokens, and secret-shaped key=value. No generic hex/base64 heuristics: those rewrite real code. - Applied at the single store write for role "bot" (text, tool.name, card title/subtitle/summary). What the user typed stays as typed — pasting your own .env for the bot to use is your call. - redactSecrets (the native tee) gains the same content pass over string values, and the events NDJSON now goes through it too. - Live streaming deltas are not redacted (tokens split across events); the settled message that replaces the bubble is, and that is what is stored and replayed. Items 2.3 and 2.4 of docs/plans/agent-harness-upgrades-v2.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…durable-delegations-redaction # Conflicts: # server/index.ts # server/store.test.ts
|
Warning Review limit reached
Next review available in: 43 minutes Limit details: You’ve used all 3 included reviews currently available under your plan. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
Comment |
main의 milind-soni#205(태스크별 토큰 지출), milind-soni#200(승인 결과 타입화), milind-soni#196(위임 영속화·비밀 마스킹) 등 7개 커밋 병합 충돌을 해결했다. - main이 busy 플래그를 activity 상태 기계로 대체함에 따라 룸 턴의 catalog 검증 실패 경로도 setActivity로 맞췄다. - antigravity respondToRequest 테스트는 main의 unavailable 해소 시맨틱을 채택했다. Tested: pnpm typecheck, pnpm vitest run (83 files, 770 passed, 8 skipped) Confidence: high Scope-risk: moderate Reversibility: moderate
In plain language
Two small safety changes.
1. Bot-to-bot handoffs survive a restart (2.3).
When one bot delegates work to another, the handoff waits until the first bot's turn finishes. Before, if the app restarted in between, that handoff silently vanished.
2. Secrets a bot writes down get masked (2.4).
If a bot's reply, a tool title, or an approval card contains something that is unmistakably a credential — an API key like
sk-…/ghp_…, a JWT, a private-key block, aBearertoken, orpassword=…— the stored copy becomes «redacted N chars». This matters because those lines are replayed to engines later (see #193); a leaked key would otherwise be permanent.«redacted 52 chars»in a bot's message or a card where a key would have been. What you type yourself is never touched — pasting your own.envfor the bot to use is your call. Ordinary code, hashes and URLs are left alone (no fuzzy matching).Summary
Two small, independent Round-2 harness upgrades in one PR, as agreed.
2.3 — Durable delegations
The per-thread handoff queue (
delegate_bot) lived in a moduleMapand died with the process — a delegation queued right before a restart never ran, silently. v1 quoted the code's own comment: "Persisted nowhere."~/.openmausbot/delegations.json(via the existingwriteFileAtomic) on queue, on drain, and on discard; loaded at boot.runDelegatedTurn, extracted from the subscriber). Target existence andapprovePeerCommsare still re-checked at drain time as before; a source bot that no longer exists is skipped. Log line: "delegations: N thread(s) with queued handoffs from a previous run — draining".2.4 — Secret redaction on what the harness stores (rescoped)
Finding that reshaped it: the harness never stores tool results — CLI drivers run tools inside the CLI; the harness only sees
item.started(name/title) anditem.completed(ok). So v1's "redact tool results before persisting" had no body to redact. What does land in the transcript — and, since #193, gets replayed into every rebuild — is the bot's reply text, activity chip titles (an ACP engine's title can be the whole command line), and permission card summaries (the command being approved). Those can carry keys.redactSecretsInText— high-precision content patterns only: known key prefixes (sk-/sk-ant-/sk-proj-,ghp_…/github_pat_,xox*-,AKIA…,AIza…,npm_), JWTs,-----BEGIN … PRIVATE KEY-----blocks,Bearer <token>, and secret-shapedkey=value/key: value(api_key,token,password,secret,authorization,client_secret…). No generic hex/base64 heuristics — those rewrite real code. Same«redacted N chars»marker the native tee already uses.appendMessage) forrole: "bot": text,tool.name, card title/subtitle/summary. User text is left as typed — pasting your own.envfor the bot is your call, and rewriting your words would be worse.redactSecrets(used by the native tee) gains the same content pass over string values; theevents/NDJSON now goes through it too.xoxb-…fixture as a real Slack token, which is a decent sign the patterns are realistic.Items 2.3 and 2.4 of
docs/plans/agent-harness-upgrades-v2.md.Test plan
redact.test.ts(+5): each key prefix; JWT / PEM / Bearer;key=valuein shell, JSON, YAML, CLI-flag forms; negatives (prose with "keyboard", a git SHA, a URL, code that mentions tokens, "password: (leave blank…)", too-shortsk-); content pass insideredactSecretsstore.test.ts(+1): bot reply, tool title, card summary masked; user text untouched; masked copy is what's on disk after reloaddelegations.test.ts(+3): queue → file; discard and drain clear it; a fresh process loads and drains what the last one queued; missing/corrupt file toleratedpnpm typecheckclean;pnpm vitest rungreen (64 files, 536 passed)sk-ant-…key stores«redacted 52 chars»; a Bearer in a command shows masked in the card and chip; my own pasted key stays🤖 Generated with Claude Code