fix(workflows): give a workflow-dispatched agent the same run identity a run has - #891
Open
arantespp wants to merge 1 commit into
Open
fix(workflows): give a workflow-dispatched agent the same run identity a run has#891arantespp wants to merge 1 commit into
arantespp wants to merge 1 commit into
Conversation
…y a run has A workflow state with `dispatch.kind: "agent"` called `createGeneration` with no `authHeader`, so an agent holding a `soat` tool had no credential for its platform self-calls: the call reached the loopback unauthenticated and came back 401. #879 fixed exactly this for the orchestration branch of the same dispatch and left the agent branch out. The agent branch now re-mints the same run-as token from the dispatch principal, keyed to the task. The token still asserts identity only — authorization is re-evaluated per call — so a chain started by a scoped API key stays bounded by that key's own policies rather than widening to its owning user. `signRunToken`/`buildRunAuthHeader` take `workPublicId` instead of `runPublicId`: the `orn` claim's *presence* is what marks a run-as token (`auth.ts`, `authScopedBoundary.ts`), and its value is a provenance breadcrumb that is never an authorization input, so it now names whichever durable work the token acts for — a run, or the task whose automation dispatched an agent. Worth knowing about the old behaviour: the AI SDK turns the thrown tool error into a `tool-error` part fed back to the model, so the dispatch still reported `completed` while the agent had actually failed. The red test asserts on the tool result the model was handed, which is where that is visible. Tests: a workflow agent dispatch whose `soat` tool must return the real listing, and the escalation mirror of `rest/soatSelfCall.test.ts` — a read-only key's read reaches 200, its write is refused 403, and no row is created. Refs #884
Deploy Outputs
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #884. First item of #889.
Problem
runDispatchcalledcreateGenerationwith noauthHeader, so a workflow state withdispatch.kind: "agent"whose agent holds asoattool had no credential for its platform self-calls — the call reached the loopback unauthenticated and came back 401. #879 fixed exactly this for the orchestration branch of the same dispatch and left the agent branch out.What the red test showed
The failing assertion, before the fix:
Worth knowing, because it changes how this reads in production: the AI SDK converts the thrown tool error into a
tool-errorpart fed back to the model, so the dispatch still reportedcompletedwhile the agent had actually failed. The task looked fine; only the answer was wrong. The tests therefore assert on the tool result the model was handed, which is the one place that is visible.Fix
The agent branch re-mints the same run-as token from the dispatch principal, keyed to the task.
taskPublicIdis threaded throughtasksAutomationRetry.ts, which already had it.The token still asserts identity only — authorization is re-evaluated per call against current policies — so a chain started by a scoped API key stays bounded by that key's own policies rather than widening to its owning user. A trigger- or OAuth-started chain records no principal and behaves exactly as before, self-calls included.
signRunToken/buildRunAuthHeadernow takeworkPublicIdinstead ofrunPublicId. Theornclaim's presence is what marks a run-as token (auth.ts,authScopedBoundary.ts); its value is never an authorization input, so it now names whichever durable work the token acts for — an orchestration run, or the task whose automation dispatched an agent.Tests
Both in
rest/soatSelfCall.test.ts, which already binds a real HTTP listener for the loopback path. A local HTTP stub stands in for the AI provider and forces thesoattool call, soai.generateTextdrives a real tool call over real HTTP — nothing internal is mocked.soattool must return the real listing;Verified the escalation test is load-bearing by mutating the mint to drop
apiKeyPublicId— the existing orchestration escalation test and the new agent one go red together.Checks
pnpm typecheck,pnpm eslint --fix— clean; noas any/as unknownadded.pnpm test— 4946 passed, 0 failed.pnpm run docs-lint— OK.No OpenAPI, SDK, CLI, permission, or formation surface changed — the fix is internal.
Docs
workflows.md— the return-edge paragraph now covers both dispatch kinds.orchestrations.md§Run identity — a workflow-dispatched agent re-mints the same token, under the same four rules.Open questions, resolved
Follow-up this surfaced
fireContinuation(agentToolApprovalContinuation.ts:141) callscreateGenerationwith noauthHeader. An approved tool call's continuation generation is genuinely request-less — the same shape as this bug, one layer down — so an agent resuming after approval has no credential for its ownsoattools.Not fixed here on purpose: closing it needs a durable principal on
Generation(an approval can resolve days later, from a different request), and that is the same surface #887 changes when it decides API-key-vs-user attribution. Writing those semantics now risks a migration. Happy to file it as a sub-issue of #889.Generated by Claude Code