Repository navigation
feat(mcp): request_secret returns usable multi-field secrets as private files - #551
Conversation
Independent review, round 1 (head 243e98d), relayed unchangedReviewer: Prism reviewer (codex, gpt-6.1-sol). Verdict: REQUEST_CHANGES. The focused tests passed: 3 files, 25 tests.
Author adjudication: all four are accepted, and fixes are coming in the next push. |
Review round 1 on #551: - A one-field save now stores its ref first and writes the file from the value the ref holds, so a racing or retried save can never leave the file and the ref with different values. - Cards keep typed values in a Map, so field names such as `constructor` read their own value; the schema rejects `__proto__`. - Values are kept exactly as typed; only blank values are refused. - The file sweep skips a directory it cannot read or remove instead of stopping for every other request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p cannot collide Review round 2 on #551: - Answers run under one lock, so two clients answering one card see one answer win, and a failed answer's cleanup cannot delete what another answer saved. - With answers serialized, a ref or directory already present on a pending card is a failed earlier save's leftover; a new save replaces both (set and replace instead of create and first-wins), so the file and the ref always hold this save's value. - A ref whose file could not be written is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review, round 2 (head 540853a), relayedReviewer: Prism reviewer (codex, gpt-6.1-sol). Verdict: REQUEST_CHANGES. The focused tests passed: 4 files, 36 tests.
Round-1 status per the reviewer: Object-property field names are fixed, trimming is fixed, and the sweep is fixed. The file/ref mismatch was not fully fixed; see 1 above. Author adjudication: both findings are accepted and fixed in c5745de. Answers now run under one lock. With answers serialized, any ref or directory left on a pending card can only be a failed earlier save's leftover, so a new save replaces both. The ref is now set rather than created, and the directory is replaced rather than kept. Three new regressions fail on 540853a and pass on c5745de. |
Independent review, round 3 (head c5745de), relayedReviewer: Prism reviewer (codex, gpt-6.1-sol). Verdict: APPROVE. It found no blocking findings in the full PR or in the round-3 delta. The focused tests passed: 4 files, 37 tests. All six prior findings are fixed: the round-1 file/ref race, Object-property field names, trimmed values and the sweep abort, and the round-2 leftover directory and failed-racing-answer cleanup. The reviewer traced these and found them sound:
It found no new value-bearing output in the cards, helpers, schemas, RPC, spans, recorded commands, MCP results, file modes or cleanup paths. The reviewer did not check visual behavior. |
…te files
request_secret gains optional fields (1-5 {name, label, placeholder?}). One
card shows every field on web and mobile, and saving is all or nothing. The
result maps each field name to a 0600 file in a 0700 per-request directory
under the environment's state directory, so the agent can use the value
without it entering the transcript. A one-field request still gets a
secretRef for schedule_task. Values are kept exactly as typed. Answers run
one at a time, so a retried or racing save always leaves the file and the
ref in agreement and never deletes another answer's save. Files expire with
the 24 hour ref TTL through the existing startup and hourly sweep, which
skips a directory it cannot read.
Rebased onto origin/main from 243e98d, 540853a and c5745de, which
were built on a stale local main.
Closes #550
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c5745de to
99070d3
Compare
Independent review, round 4 (rebased head 99070d3), relayedReviewer: Prism reviewer (codex, gpt-6.1-sol). Verdict: APPROVE. It found no blocking findings. The focused tests passed: 4 files, 38 tests.
Author adjudication: the P3 is optional and stays as is. Removing it would only produce a new head that needs another review. |
|
Agent cost: |
The Fork Stack Model check requires every edited upstream file to be allowlisted and owned. Adds the eight secret-request files to scripts/fork-upstream-edits.txt and a usable-secret-requests entry in docs/fork-features.md, sharing the files other features already own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent review, round 5 (head 410d91d, fork registry delta), relayedReviewer: Prism reviewer (codex, gpt-6.1-sol). Verdict: APPROVE. It found no blocking findings.
|
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |




What:
request_secretnow hands the agent a usable secret: one card can ask for 1-5 fields, and the result gives a private file path for each field.Why: Before this, a saved value could only be used as a one-use
secretRef, and onlyschedule_task's webhook signature accepted one, so agents didn't use the card for ordinary credentials.So what: Ready for the user's merge approval: independent review passed and CI is green on 410d91d. Before/after images of the web card still need a browser pass, which this run was not allowed to do.
Closes #550
Problem
An agent asking for a token, API key or login could only get a
secretRef, and nothing used it except webhook signing. So agents either asked in chat or gave up.Fix
request_secrettakes optionalfields(1-5{name, label, placeholder?}; names are unique and match[a-z0-9_-]+). The turn item and the internalsecret_request.recordcommand carry the field metadata, never values.secrets.answerRequestaccepts{type:"save", secret}for single-field cards, as before, and{type:"save", secrets:{name: value}}for multi-field cards. A newfields_mismatcherror covers a missing or extra field. Values are kept exactly as typed, spaces included; only blank values are refused. Field names cannot be__proto__.SecretRequests): a save first checks that every field is present (all or nothing). It then writes each value to its own 0600 file inside a 0700 directory,<stateDir>/secret-requests/<ref-hex>/<field>. That path is in the environment's T3 home userdata tree, never a worktree. Answers run one at a time under a lock. The files are written to a temp directory and renamed into place, so the agent never sees half a save. A leftover from a failed earlier save of the same card is replaced. A declined, cancelled or lost-race request, or a failed record, leaves no files.{status:"saved", files:{name: absolutePath}, secretRef?}. A request withoutfieldsreturns the keysecret.Map, so a field named like an Object property, such asconstructor, reads its own value, and render one input per field through the sharedsecretRequestFields/secretRequestAnswerInputhelpers in client-runtime. A single-field card looks the same as before. The privacy note now says "Sent privately to the agent, never shown in chat".packages/provider-core/src/server/orchestrationInstructions.tson current main) tell agents to userequest_secretfor any secret, to use the file without printing it ("$(cat path)",bao kv put … field=-,rbw add), and to delete it when done.Decisions the Issue left to the worker
secretRef. That covers nofields, orfieldswith one entry. A ref has always carried exactly one value, andschedule_task's signature needs exactly one. Multi-field requests get onlyfiles. Because answers are serialized, a retried save replaces both the ref and the file, so they always hold the same value, and a failed answer cannot delete what another answer saved.ScheduledTaskServiceneeds no change.request_secretis still forwarded by the genericMcpRoutingpath, unchanged. A forwarded call returns paths on the destination environment, which only an agent on that host can read. Cross-machine routing stays out of scope (feat(peer): request destination secrets and create signed webhooks without a destination agent #385).Upstream
pingdotgg/t3code#15907 (merged) is the current card, and #17410 (closed) was a mobile paste button. Upstream has no work on returning usable values or on multi-field cards.Elon record
SecretRequests.ts,secretRequest.ts, therequest_secrettool and handler,ScheduledTaskService.ts(the secretRef consumer),McpRouting.ts(peer forwarding) and both cards. Ran the focused tests and typechecks below on this branch.Proof (head 410d91d: feature commit 99070d3 plus a fork-registry commit, on origin/main e5d864c)
The first three heads (243e98d, 540853a, c5745de) were built on a stale local
mainthat had diverged fromorigin/main, so the PR showed 61 commits and 610 files. The change is now one commit replayed ontoorigin/main. Onmain, these files had only lost comments, mobile imports had moved undercomponents/ui, and the instructions file had moved topackages/provider-core. The old history is kept locally onbackup/550-on-stale-main.vp test run apps/server/src/secrets/SecretRequests.test.ts packages/client-runtime/src/secretRequest.test.ts packages/contracts/src/orchestratorMcp.test.ts apps/server/src/mcp/OrchestratorMcpToolkit.integration.test.ts apps/server/src/scheduledTasks/ScheduledTaskService.webhook.test.ts apps/server/src/peer/PeerToolRouting.test.ts apps/server/src/mcp/OrchestratorMcpService.test.ts apps/server/src/mcp/toolkits/core.test.ts apps/server/src/orchestration-v2/ProjectionStore.test.ts: 9 files, 194 tests passed. They cover a single-field save that also leaves a file; a multi-field save with file contents, 0600 file modes, a 0700 directory, no ref, and no values in the recorded command; a missing or extra field writing nothing; decline and a lost race to the card closing leaving no files; values kept exactly as typed; retries after a failed record replacing a leftover ref and a leftover directory; a failed racing answer leaving the winner intact; the sweep removing expired and emptied directories and skipping an unreadable one; field-name schema limits; Object-property field names; and, at the MCP seam, values absent from the tool result and from the whole thread projection, with the files readable.vp run typecheckexits 0 inpackages/contracts,packages/client-runtime,packages/provider-core,apps/server,apps/webandapps/mobile.vp fmt --checkandvp linton the touched files are clean. The only lint warning,Orchestrator.tslayerUnavailable, was already on main.review/independentis success on that head.scripts/fork-upstream-edits.txtanddocs/fork-features.mdregistered the 8 edited upstream files.Website impact: none. This changes an agent tool and an in-app card, with no marketing-site content or URLs.
Model and harness: Claude Opus 5.5 in Claude Code (T3 Code).
🤖 Generated with Claude Code