Repository navigation
feat(auth): authenticate from DEPOT_API_TOKEN_URL in a Dispatch sandbox - #689
Conversation
A Dispatch sandbox sets DEPOT_API_TOKEN_URL, an address that answers with a short-lived token acting as the running turn's sender. The CLI now reads it through the same path as DEPOT_RUN_TOKEN_URL when no --token, DEPOT_TOKEN or saved login is present, so `depot agent list` works there with no setup. DEPOT_RUN_TOKEN_URL is preferred when both are set. A failed fetch prints an error naming the variable; errors never carry the URL, the response body or a token. DEP-8526 Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RQk3K7GpvQg7ySXyCrirEe
A transport error can quote the request URL or a response header, so a failed fetch now reports a category (timed out, could not connect, failed) instead of the error's text, and redirects are not followed. DEP-8526 Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RQk3K7GpvQg7ySXyCrirEe
The turn token from DEPOT_API_TOKEN_URL is accepted by the session API alone, so resolving it for every command replaced a sandbox's working build and cache credentials. It now has its own source, consulted only by `depot agent` commands, ahead of the cache token. Every other command resolves exactly what it did before. Fetch errors keep cancellation and timeout in the error chain without the transport text, and a token source follows its variable instead of being read once per process. DEP-8526 Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RQk3K7GpvQg7ySXyCrirEe
The API takes a turn's token for GetSession, GetSessionMessage, ListDepotAgentSessions and CreateSession only. Launch and SendInput do not take it, so `depot agent launch` and `depot agent send` resolve credentials as they do on main. Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RQk3K7GpvQg7ySXyCrirEe
A Dispatch turn's own token is accepted by CreateSession, GetSession, GetSessionMessage and ListDepotAgentSessions only. Those calls send it; WatchSession, InterruptSession and ArchiveSession send the token every other command resolves, as before. A command that makes both kinds of call (create --watch, watch) picks per call, and fails before sending anything when it has no token for one of them. stop no longer reads the turn token at all. Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RQk3K7GpvQg7ySXyCrirEe
A session created with a turn's token and then refused its watch would be left running, and each retry would add another. create --watch now resolves its token as it does on main and uses it for every call. Co-Authored-By: Claude Mythos 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RQk3K7GpvQg7ySXyCrirEe
There was a problem hiding this comment.
I found no new bugs in this version. My earlier create --watch finding is addressed. It changes how credentials are picked for each call, so a human should still look before merging.
What was reviewed:
create --watchnow resolves throughorgCaller, soCreateSessionandWatchSessiongo out with the same token.createwithout--watch, pluslist,logsandwatch, send the turn token only on the four RPCs named inasTurn.DEPOT_API_TOKEN_URLis read only inResolveAgentAuth, soResolveProjectAuthandResolveStaticOrgAuth(and with themdepot build) rank credentials the same way as before.fetchErrorkeepscontext.CanceledandDeadlineExceededin the error chain without the URL or the response body.fromEnvnow reads the env var on every call, so a latert.Setenvin a test takes effect.
Extended reasoning...
The change adds a second sandbox token source (DEPOT_API_TOKEN_URL) in pkg/runtoken and uses it for a subset of the depot agent session RPCs, through ResolveAgentAuth and the new caller/orgCaller split. It touches authentication: which token goes on which API call, what order credentials are tried in, and keeping token endpoint errors from leaking the URL or response body. The latest commit fixes the create --watch issue raised earlier, and the earlier precedence, error-chain and test-caching points also hold in the current code. I deferred rather than approved because this is auth code, and the per-RPC choice depends on server-side behaviour that cannot be checked from this repo.
|
From Claude: merge gate proof (delegate approval).
|
|
Correction to the merge record: the bot approval on this PR was given on 7d1e159, four commits before the merged head 619d73a. At the merged head the review bot commented that it found no new bugs and that its earlier From Claude. |
|
Follow-up to the correction above, after checking the check runs on 619d73a: both From Claude. |


DEP-8526
In a Dispatch sandbox
depothad no credentials, sodepot agent listfailed with "missing API token". The sandbox does carryDEPOT_API_TOKEN_URL, an address that answers with a short-lived token acting as the running turn's sender. Thedepot agentsession calls the API accepts that token on now send it.CreateSession,GetSession,GetSessionMessageandListDepotAgentSessionstake the turn token, soagent create,listandlogsuse it.WatchSession,InterruptSession,ArchiveSession,SendInputandLaunchdo not, so those calls, and every other command (depot build, cache tools,configure-docker), send exactly what they did before.agent watchmakes both kinds of call and picks for each.create --watchsends every call as before, so it never creates a session with one token that it is then refused a watch on with another.--token, thenDEPOT_TOKEN, then a saved login, thenDEPOT_RUN_TOKEN_URL, thenDEPOT_API_TOKEN_URL, then the sandbox's cache token. Nothing explicit is ever overridden.DEPOT_RUN_TOKEN_URLfirst. It is set for this CLI on purpose and its token reaches every org API; the turn token is ambient in the sandbox and narrower. A sandbox normally has only one of them, and managed runs keep today's behaviour.DEPOT_RUN_TOKEN_URL(pkg/runtoken), one instance per variable.error: could not fetch a token from DEPOT_API_TOKEN_URL: the endpoint answered HTTP 503, then the usual missing-token error.The URL is requested exactly as given: the endpoint answers for the first
audienceparameter only.Agent context, proof and test plan
What the address expects (depot/api at 7ff33e65c0)
src/domains/depot-agent-identity/runtime/block.ts:28,174setsDEPOT_API_TOKEN_URLto the sandbox metadata address plus/token?audience=depot-api.src/utils/workloadIdentity.ts:9: the audience isdepot-api.src/domains/identity/service/oidc-mint.ts:59-71andsrc/authn/oidc.ts:235-257require it to be the only audience.{"value":"<token>"}, the same shapeDEPOT_RUN_TOKEN_URLanswers with, so the CLI does a plain GET of the variable's value and adds nothing to it.src/domains/depot-agent-identity/README.md:64-76: the token acts as the turn's sender and is accepted by a short list of services, session listing among them.Which API call takes the turn token (depot/api at 7ff33e65c0)
agent createCreateSessionsrc/domains/depot-agent/runtime/depot.agent.v1.DepotAgentService.ts:78,:84)--watchagent listListDepotAgentSessions:230)agent logs,watchGetSession:204)agent logs,watchGetSessionMessage:220)agent watch,create --watchWatchSession:244)agent stopInterruptSession:141)agent stop --archiveArchiveSession:151)agent sendSendInput:119)agent launchLaunchsrc/domains/depot-agent-launch/runtime/launch.ts:14)A call the API does not take the token on would answer "This Depot API call can't be made as a Depot agent turn's sender yet" (
src/authn/resolveToken.ts:195,210), so the CLI never sends it there.--token,DEPOT_TOKEN, a saved login,DEPOT_RUN_TOKEN_URL, then the sandbox's cache token.create --watchuses one token forCreateSessionandWatchSession. In a sandbox that is the cache token, which the session API rejects, so the command fails atCreateSessionas it does on main and no session is left running unwatched. With no token at all it fails with the missing-token error before it sends anything.agent stopmakes no call that takes the turn token and never reads the address.send --watch,launch --watchandcreate --watchkeep one token for all their calls, as on main.pkg/cmd/agent/credential_test.goruns every command with only the token address and a cache token set and asserts the credential each call arrived with.Proof: a real Dispatch sandbox, binary built there from this branch at e2ed4ee
Commits after this run narrow which calls send the turn token (the table above).
agent list, the command these runs exercise, resolves its credential the same way.The sandbox agent cloned this branch, built it (Go 1.26.8, linux/amd64) and ran it. The sandbox environment had
DEPOT_API_TOKEN_URLset and noDEPOT_TOKEN.In 2 and 3 the second line comes from the sandbox's build-cache token, which the CLI falls back to as before and which the session API rejects. Run 5 is a command outside the session API: it never asks the token address, so no error names the variable and it gets the same answer as on the previous release. "6 files" counts files under
~/.config/depotafter the runs.Proof: this branch's binary on a laptop against the real API, empty config directory, token address served on localhost
/token?audience=depot-api.--tokengiven with the address pointing at the local server: the server saw 0 requests.depot list projectswith only the address set: "missing API token" as before, the server saw 0 requests.Not covered here
depotalready installed in a sandbox is an older release, sodepot agent listwith no setup works there only once a release with this change is installed.DEPOT_RUN_TOKEN_URL. A sandbox has no saved login.agent watch,create --watchandstopfrom inside a sandbox need the API to take the turn token onWatchSession,InterruptSessionandArchiveSession. When it does, each call is a one-word switch fromauthedtoasTurn.agent listwas run with a real turn token.createandlogswere not; the per-call choice is covered by unit tests against a local server.Post-deploy test plan
Nothing deploys: the change ships in the next CLI release. Once a release containing this commit is the
depoton a Dispatch sandbox's PATH:depot agent create "Run: depot agent list; echo exit=\$? and reply with the output" --watchon any org. Expected: the reply holds a session table andexit=0.DEPOT_API_TOKEN_URL=http://169.254.169.253/nope depot agent list. Expected: stderr startserror: could not fetch a token from DEPOT_API_TOKEN_URL, exit 1.DEPOT_API_TOKEN_URL=http://127.0.0.1:9/token depot list projects. Expected: no line namingDEPOT_API_TOKEN_URL; the command behaves as it does on the previous release.DEPOT_API_TOKEN_URL=http://127.0.0.1:1/ depot agent list. Expected: works as before, the URL is not contacted.🤖 Generated with Claude Code
https://claude.ai/code/session_01RQk3K7GpvQg7ySXyCrirEe
Note
Medium Risk
Changes authentication precedence and which token is sent per agent API call; mistakes could break sandbox agent commands or leak overly broad credentials to the wrong RPCs.
Overview
Enables
depot agent(and related session RPCs) inside Dispatch sandboxes by minting credentials fromDEPOT_API_TOKEN_URL, using the same fetch/refresh path asDEPOT_RUN_TOKEN_URL.runtokennow keeps a separate source per env var, wraps HTTP clients to refresh both run and turn tokens, blocks redirects, and surfaces fetch failures as variable-named errors that omit URLs and response bodies.ResolveAgentAuthreturns an org-scoped token and a narrower turn token; agent commands pick per RPC viaasTurnforCreateSession,GetSession,GetSessionMessage, andListDepotAgentSessions, and the existing org token for watch/stop/send/launch. Commands that need only turn-token RPCs can succeed with just the turn URL; mixed commands fail fast with missing API token when no cache/org credential exists.Integration tests assert the Bearer header each agent subcommand sends matches what the API accepts.
Reviewed by Cursor Bugbot for commit 619d73a. Bugbot is set up for automated code reviews on this repo. Configure here.