Skip to content

🤖 feat: Daytona sandboxes as a workspace runtime - #7100

Open
ibetitsmike wants to merge 233 commits into
mainfrom
mike/daytona-runtime
Open

ibetitsmike wants to merge 233 commits into
mainfrom
mike/daytona-runtime

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds Daytona Cloud as a workspace runtime. A Daytona workspace is an ssh workspace with a daytona sub-config: Xum creates a sandbox through Daytona's REST API and reaches it through Daytona's SSH gateway, so bash, file tools, git, background jobs, terminals and sub-agents run in the sandbox instead of on the Xum host. It reuses all of SSHRuntime, as Coder does.

What the user gets

  • Settings > Runtimes > Daytona: API key (write-only), Test connection, Remove key. Without a saved key Xum uses DAYTONA_API_KEY; DAYTONA_API_URL and DAYTONA_TARGET pick a self-hosted API and a region.
  • New workspace: a Daytona runtime with snapshot and size (Small 2/4/10, Medium 4/8/20, Large 8/16/40 vCPU/GiB/GiB disk). Windows and GPU snapshots are refused.
  • Lifecycle: a turn or terminal starts a stopped sandbox; Daytona stops it after 30 minutes idle (Xum reports activity while a turn streams or a job runs). Archiving or removing the workspace that created the sandbox deletes it; an agent cannot archive it. Forks and sub-agents get a worktree in the same sandbox.
  • Footer chip (running, stopped), open in editor, and docs/runtime/daytona.mdx.

Size

Down from +15.0k/-0.7k to +10.2k/-0.5k: production +5.5k, tests +4.6k, docs and stories +0.2k. An earlier revision removed about 5.5k lines of review-driven edge-case handling (unowned-sandbox paths, connection retirement, the account lease, generation counters, ssh2 Include parsing, project defaults, event-driven status refresh). The commit messages say what each cut gives up. 69803bc adds the recorded sandbox id (+0.3k) and cuts the reconciler's token refresh.

Testing

  • Real Daytona: tests/runtime/daytonaRuntime.test.ts (create, exec, files, fork, archive, delete through the real gateway) passes on 69803bc. On that head it found that Daytona keeps answering by id for a deleted sandbox (state deleting, then destroyed); those states now count as gone. Earlier it found one bug, fixed in e7da6a3: SSHRuntime spawned git bundle create before the remote cat started, so a slow SSH start (Daytona's gateway) uploaded an empty bundle. Red-green test in SSHRuntime.syncContract.test.ts.
  • Remote UAT round 8 (PASS, on 69803bc, live Daytona, Coder Agents on dogfood): A1 create records Daytona's id as sandboxId and the alias as sandboxName; a Haiku turn and the terminal ran in the sandbox. A2 a stop outside Xum, then the next turn started the sandbox and kept its files. A3 name takeover: Xum reports the sandbox gone and never starts, keeps alive or deletes the new sandbox with the old name. A4 a fork shares the sandbox; only the creator's archive deletes it. A5 an archive during the create leaves no sandbox. A6 a server restart keeps the id. A7 no key or token in any log or attachment. A8 no sandbox, ssh access or config left behind. In the fault-injection runs, 38 requests that change a sandbox went by id and 0 by name. The commits after 69803bc change only the docs (and the docs skill generated from them).
  • Local UAT (PASS, on 44309a0, before the last three cuts): dev server with a temp HOME; key from env, Test connection, create (ready in 19 s), a Haiku turn ran bash in the sandbox, integrated terminal on the sandbox, footer chip, archive deleted the sandbox (API 404), creation form at 390 px.
  • make static-check, make lint-eslint, make crap-check, and the Daytona, SSH, terminal and touched browser suites pass (bun 1.4.3).

Review record

  • Codex rounds before this revision: findings fixed (P0/P1) or filed (P2) per thread; round 7 on 3c17aee was clean.
  • Codex on a1d7073 (after the cuts): 3 P1 fixed (a token split across output chunks, 328b7fe; sandboxes without this install's label, and a settings save during a create, bd8c2a8). 6 P2: 4 describe losses the cuts chose, 1 matches the no-orphan-sweep decision, 1 filed as 🤖 fix: keep SSH access to a Daytona sandbox when its delete fails #7451.
  • Codex on bd8c2a8: 2 P1 fixed in 1c26b34. A Daytona terminal's ssh runs with LogLevel=QUIET, so ssh never prints the token into the terminal (the PTY redactor and its hold timer are gone). A settings save during a create now fails that create and deletes its sandbox in the old account.
  • Codex on 1c26b34: 1 P1 fixed in 07c785d. A token mint still running when a settings save lands now removes its alias, revokes the token with the old key and fails its caller, so no old-account token outlives the save. 1 P2 filed as 🤖 fix: removing a Daytona fork while its sandbox is stopped leaves the fork's checkout behind #7457 (a fork removed while its sandbox is stopped leaves its checkout).
  • Codex on 07c785d: 1 P1 fixed in dcc2630. The delete goes to the sandbox id the ownership check read, so a sandbox that takes the name after the check is never deleted, and a sandbox already gone gets no delete. 1 P1 (an idle AI proxy forward keeps a sandbox billed) cannot occur: a live probe showed that Daytona's gateway refuses every remote forward, and an idle connection does not stop auto-stop. 270e373 stops Xum from trying the forward on Daytona, which made a bash command wait up to 10 s every 5 minutes with the proxy on. 2 P2 filed as 🤖 fix: a Daytona setConfig call without an API key still logs every sandbox out of ssh #7462 (a settings call without a key still revokes ssh access) and 🤖 fix: Daytona startAndWait can start a sandbox after its deadline and then report a timeout #7463 (a start sent after the deadline). 1 security P2 answered as intended: the AI proxy authorizes each request by workspace key, trust and kill switch, not by Daytona credentials.
  • Codex on 270e373: 1 P1 fixed in bdcf0f0. The resize fallback stops, resizes and restarts the id the create returned, and startAndWait starts the id whose label it checked, so a sandbox that takes the name is never changed.
  • Codex on bdcf0f0: 1 P1 fixed in ebe4781. The managed ~/.ssh/config block sets LogLevel QUIET for sandbox aliases, so an editor's or a native terminal's ssh never prints the token in an error. 1 P1 answered as intended: the archive deletes the sandbox before it persists archivedAt, as Coder's delete-on-archive hook does, so a failed delete blocks the archive; the shared gap after a failed config write is 🤖 fix: an archive whose config write fails after a hook deleted the remote box leaves the workspace active #7465.
  • Codex on ebe4781: 2 P1 fixed in 69803bc (a reconciler token mint by name, and a mint that crosses an access release). The create records Daytona's sandbox id in the workspace config (daytona.sandboxId; the alias name is now daytona.sandboxName), and every control-plane request goes to that id, so a sandbox that takes the name is never reached. The reconciler's token refresh is cut: every mint now runs in the readiness join that an access release waits for.
  • Codex on 69803bc: clean ("Didn't find any major issues"), security review clean.
  • UAT round 8 low findings: the docs said Xum refreshes the ssh token before it expires, which stopped being true when the reconciler's refresh was cut; f0fec18 fixes the wording. Not fixed: a sandboxId hand-edited to another Xum sandbox's id makes the delete act on that sandbox (Xum acts on the recorded id by design), and a turn sent while Daytona destroys the sandbox makes one failed ssh attempt before it reports the sandbox gone.
  • Codex on f0fec18: clean ("Didn't find any major issues").

Follow-ups

#7230 (cap API response size), #7232 (record the creating account), #7236 (one keepalive per shared sandbox), #7250 (rename on a stopped sandbox), #7252 (stale connection-test result), #7254 (status chip refresh after a turn, terminal or editor starts the sandbox), #7451 (SSH access after a failed delete), #7446 (a user fork gets its own sandbox), #7457 (fork removal on a stopped sandbox), #7462 (settings call without a key), #7463 (start after the deadline), #7465 (archive after a failed config write).

Risks

  • Shared SSH code: plain SSH and Coder use the bundle-upload fix, the ~ path fixes and the managed ~/.ssh/config block writer. Host-key pinning, redaction and token handling act only on Daytona aliases.
  • Archive is permanent for the workspace that created the sandbox, including the worktrees of its forks.
  • Billing: sandboxes are billed while running; auto-stop bounds it.

Settings: Daytona key from env, Test connection OK

New workspace with the Daytona runtime

Agent bash output from the sandbox

Creation form at 390 px

…sh config

Part A of the Daytona runtime provider (ssh + daytona sub-config):

- runtime schema: optional `daytona` sub-config on the ssh variant, mutually
  exclusive with `coder`; `daytona` runtime enablement ID (UI-only, like coder)
  and a shared enablement-ID -> RuntimeMode record.
- shared size presets (small/medium/large) in src/common/constants/daytona.ts.
- credentials in <xum home>/daytona.json (0600, atomic), with
  DAYTONA_API_KEY / DAYTONA_API_URL fallbacks; the UI only sees a masked view.
- `daytona` oRPC namespace: getConfig, setConfig, testConnection,
  listSandboxes, listSnapshots.
- thin typed fetch client with timeouts and typed errors (no @daytona/sdk).
- DaytonaService: labels with the installation identity, per-sandbox SSH token
  cache with refresh-then-revoke, startAndWait with bounded polling.
- daytonaSshConfig: 0600 include file with one Host block per sandbox, gateway
  known_hosts, a managed `Host *.xum--daytona` + Include block in ~/.ssh/config
  (shared marker-block writer extracted from the Coder writer), and an
  `ssh -G` check that the token is the resolved user.
- sshConfigParser (ssh2 path) now resolves Include directives.
…and wiring

Part B of the Daytona runtime provider (ssh + daytona sub-config):

- DaytonaSSHRuntime (mirrors CoderSSHRuntime): finalizeConfig names a new
  sandbox before persist (Daytona takes a name wherever it takes an id) or
  attaches a given one as shared; validateBeforePersist checks credentials,
  preset size and name/attach existence; postCreateSetup creates with preset
  resources and lifecycle intervals, falls back to create-then-resize on HTTP
  400, waits, writes ssh access, seeds the agent home and deletes the sandbox
  it created on failure; ensureReady caches a ready result for 60 s per
  sandbox, shares one in-flight start, reports "Starting sandbox" and maps a
  missing sandbox to runtime_not_ready; forkWorkspace marks both shared;
  deleteWorkspace starts a stopped sandbox so the dirty/unpushed checks run.
- DaytonaService: create/resize/stop/delete/refreshActivity, single-flight
  ensureSandboxReady, ssh include ownership with token refresh, ready cache.
- Agent-home seeding of tool_env/tool_pre/tool_post and global skills to the
  paths remote lookups read (~/.mux, ~/.agents/skills), never for attached boxes.
- runtimeFactory selects DaytonaSSHRuntime; checkRuntimeAvailability reports
  Daytona availability; bash AI proxy forwards are not restored at startup.
- Archive reuses the Coder keep/stop/delete behavior for dedicated sandboxes.
- DaytonaReconciler (service container, 60 s, never blocks startup):
  keepalive for active workspaces, token refresh, stop-only orphan sweep with
  archived workspaces counted as references, per-call timeouts.
- Gated TEST_DAYTONA=1 real test (skipped by default).
…e in archive refusals, report sandbox states

Archive with the delete behavior now runs the same dirty and unpushed checks as
deleteWorkspace on a dedicated Daytona sandbox (starting a stopped one for
them). When they fail, the sandbox is stopped instead and the archive result
carries a warning the sidebar and menu bar show. Archive refusals name the
Daytona sandbox instead of a Coder workspace, and getRuntimeStatuses reports
Daytona sandbox states (starting, running, stopped, archived, error).
…ontrols

Settings > Runtimes gets a Daytona row: a global access form (write-only API
key in its own file, API URL, target, test connection) and a project-scope
defaults form (snapshot and size preset, saved to lastRuntimeConfig.ssh.daytona).
Workspace creation gets a Daytona runtime button with New/Existing sandbox,
snapshot and size controls, prefilled from the project defaults. Runtime badges
and the footer chip show the sandbox state. Stories cover the forms at desktop
and 390px widths as play-only contracts (Pixel budget has no headroom).
…g a shared-checkout child

Side chats and isolation:none sub-agents copy the parent's runtime config,
sandbox ID included, without the shared flag, so archiving one stopped or
deleted the parent's sandbox.
…ook path

The hooks page said remote user-level hooks live in ~/.xum; SSH remotes read
~/.mux and Docker reads /var/mux (RemoteRuntime/DockerRuntime getXumHome).
… and docs index

Rebuild the runtime-choice context inside the auto-switch effect so it depends
on its fields, not a per-render object; format; sync the xum-docs skill index.
Normalizing the project's default runtime in render broke the compiler's
memoization, so selectedRuntime changed identity on every keystroke
(useDraftWorkspaceSettings.compiler.test.tsx). The config type already
admits daytona, so the normalization was unnecessary.
…RAP limits

Extract the SSH badge, runtime-choice table, Coder/Daytona creation
validation, Daytona access hooks and components, archive result helpers,
the remembered-SSH merge, the delete-check exit-code mapping and the
Daytona status probe. The creation form now loads Daytona lists itself
while mounted. Covers the archive warning popover path with a test.
…s from starting sandboxes

- Background git and PR status wait for a Daytona sandbox to run, as for
  devcontainers, so opening a workspace no longer starts a stopped sandbox.
- RuntimeStatusStore refreshes when a turn starts a stopped runtime and when
  that start ends (runtime-status events from the workspace chat stream).
- The API key status sits beside its label instead of inside it.
…clear errors

- An invalid API URL throws DaytonaConfigError, which daytona.setConfig
  returns as BAD_REQUEST with its reason instead of Internal Server Error.
- Client errors drop HTML tags, collapse whitespace and keep 200 chars of
  the server message.
…e findings

- Delete refuses a dirty worktree under the unresolved ~/... path:
  checkWorkspaceDeletable expands a leading ~ remotely (also fixes Coder),
  and Daytona resolves the path first and fails closed when it cannot.
- Daytona aliases keep their pinned gateway key: the headless OpenSSH
  override skips *.xum--daytona, and ssh2 checks the server key against the
  pinned daytona_known_hosts entries.
- Daytona ssh tokens are redacted (<daytona-token>) from ssh stderr, errors,
  init output, pool probe errors and reverse-forward logs.
- A fork reuses its source's sandbox (`forked`) without marking either
  workspace attached; the last referencing workspace, archived ones
  included, deletes it. Attached sandboxes are never deleted; their last
  workspace only releases Xum's ssh access. Archive skips a sandbox another
  workspace references.
- A turn's runtime restarts a sandbox that stopped within the ready cache
  and retries the command once; transport failures invalidate the cache.
- Tokens are revoked on settings changes and failed installs; a failed
  ssh -G user check reuses the token on retry.
- Later turns after a failed setup report the setup failure and its reason.
- Keepalive checks whether background jobs still run, skips stopped or
  unknown sandboxes, and skips the sweep when the sandbox list fails.
`new URL` reads "http:///api" and "https:api" as host "api", so they were
saved as http://api. normalizeApiUrl now requires a written host and throws
DaytonaConfigError ("has no host") otherwise.
…ings

- Removing the last workspaces on one sandbox at the same time deletes it
  exactly once: the fate decision runs under a per-sandbox lock, and a
  removal that passed its dirty check is recorded so the next one does not
  count it. A refused (dirty) removal is never recorded and keeps the sandbox.
- Startup loads every token from daytona.conf into the redactor and the
  token cache (due for refresh), so ssh errors after a restart never show
  it, and the next mint, a settings change or an archive revokes it.
- When the gateway refuses a token (revoked outside Xum), the runtime mints
  a new one, rewrites the include file, resets the pool backoff and retries
  the command once.
- A forced delete whose sandbox DELETE fails returns a removal warning that
  names the sandbox to delete by hand.
- Delete waits (up to 60 s) for a sandbox Daytona is already destroying
  instead of failing with "is in state destroying".
…token

A stopped or deleted sandbox also answers "Permission denied", so a
command sent within the ready cache after a stop minted and revoked a
token for nothing and then failed, and the first command after a delete
outside Xum showed the refusal instead of "no longer exists". The runtime
now reads the sandbox state first on an ssh failure: a stopped sandbox
takes the restart path (turn runtimes), a missing one reports that it no
longer exists, and only a started sandbox gets a new token.
…l on their claimed breaks

- await the background-runtime rejection so its no-start assertion runs after the command settles
- assert both concurrent ensureReady checks report the shared start (the old test passed without the dedupe)
- assert the clean-delete test runs the SSH dirty check
- round-trip snapshot, target and forked through RuntimeConfigSchema
@mintlify

mintlify Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
Mux 🟢 Ready View Preview Oct 27, 2026, 3:17 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@chatgpt-codex-connector

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: 26fb323354

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/node/runtime/daytonaSshConfig.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26fb323354

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/daytona/daytonaService.ts Outdated
Comment thread src/node/runtime/DaytonaSSHRuntime.ts Outdated
Comment thread src/browser/hooks/useDaytonaResources.ts Outdated
Comment thread src/node/runtime/daytonaTokenRedaction.ts Outdated
Comment thread src/node/runtime/sshConfigParser.ts Outdated
setConfig revoked only cached tokens; a mint in flight finished afterwards and wrote and cached an old-account token. A credentials generation is captured at mint start: a mint that spans a change revokes its token with the client that minted it, never writes or caches it, and mints again for the waiting callers.
…ours

A name taken between validateBeforePersist and the create made Daytona refuse the create, and the cleanup then deleted the other install's sandbox. A failed create now deletes only a sandbox carrying this install's and workspace's labels; a successful create is still deleted when a later setup step fails.
The 256-token cap evicted tokens that were still live. Tokens now expire by age only.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07c785d9e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/daytona/daytonaAccount.ts
Comment thread src/node/services/bashAiProxy/sshForwardTarget.ts Outdated
Comment thread src/node/services/daytona/daytonaSandboxes.ts Outdated
Comment thread src/node/services/daytona/daytonaSandboxes.ts
The workspace config holds the sandbox name, which another sandbox can
take between the label check and the delete. Delete by the id the check
read, and send no delete for a sandbox that is already gone.
Daytona's ssh gateway refuses every remote port forward (OpenSSH -R with
any bind address, ssh2 forwardIn; checked live). Trying anyway made a
bash command wait up to 10 s every 5 minutes while Xum tried 4 ports.
A Daytona runtime now gets no forward target, as Docker does.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 270e373356

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/runtime/DaytonaSSHRuntime.ts Outdated
The workspace config holds the sandbox name, which another sandbox can
take. The resize fallback now stops, resizes and restarts the id that
the create returned, and startAndWait starts the id of the sandbox whose
label it checked. Also regenerates the built-in docs for the providers
page change in 270e373.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdcf0f0dac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/daytona/daytonaLifecycleHooks.ts Outdated
Comment thread src/node/runtime/daytonaSshConfig.ts
OpenSSH names the user, which is the gateway token, in its fatal
Permission denied line, and an editor's or a native terminal's ssh
output passes no Xum redactor. The managed ~/.ssh/config block now sets
LogLevel QUIET for sandbox aliases. Xum's own ssh reads only the include
file, so its messages (redacted) stay.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ebe478135b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/daytona/daytonaReconciler.ts Outdated
Comment thread src/node/services/daytona/daytonaSsh.ts Outdated
@ibetitsmike

ibetitsmike commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor Author

Review loop paused (AGENTS.md early stop, case 2: the loop does not converge). This is not a ready state. Merge stays paused.

Codex rounds on the cut tree found 6, 3, 2, 2, 2, 5, 1, 2 and now 2 findings. The list stopped getting shorter, and the last rounds keep finding new cases of the same two design properties:

  1. Xum identifies a sandbox by its name. The workspace config stores the name that Xum chose (xum-<branch>-<6 hex>), and Daytona lets another sandbox take a name after the first one is gone. So each call that acts after a label check can reach a different sandbox: delete (fixed in dcc2630), resize and start (fixed in bdcf0f0), and now the reconciler's token mint (open, daytonaReconciler.ts:189).
  2. Token mints run outside the release. The reconciler's background token refresh can race a settings save (fixed in 07c785d) and now an archive or removal (open, daytonaSsh.ts:184).

Both open findings go through the reconciler's token refresh. The fixes so far were small, but the next ones add mechanism: coordinating a release with a mint is the kind of tracking that this revision removed. So I stop here and ask for a decision:

  • A. Accept and defer. Treat name reuse as outside the threat model: only someone with API access to the organization can create a sandbox with Xum's generated name, and that person can already reach every sandbox in it. File the release and mint race as a follow-up; a token that outlives a release stays valid for at most 60 minutes.
  • B. Cut the reconciler's token refresh. That removes the trigger of both open findings. Tokens then refresh only when Xum itself connects: after a token expires (60 minutes), new ssh connections from an external editor or a native terminal fail until Xum next connects to that workspace. The activity refresh that keeps busy sandboxes running stays.
  • C. Store the sandbox id from the create response and use it for every call. That closes class 1 for good, but it adds a persisted field (scope growth under AGENTS.md rule 3), so it would be a stacked or follow-up PR.

The two open threads stay open until this is decided.

# Conflicts:
#	src/browser/stores/GitStatusStore.test.ts
#	src/cli/serverSecurity.ts
#	src/node/services/serviceContainer.test.ts
#	src/node/services/workspaceService.ts
The workspace config held only the sandbox name, which another sandbox
can take once the workspace's own is gone. The create now records
Daytona's id as daytona.sandboxId (the name moves to daytona.sandboxName,
which the ssh alias keeps using), and every control-plane request about
a workspace's sandbox goes to that id: state, start, stop, resize,
delete, activity, and ssh token mint and revoke. A sandbox without a
recorded id is not Xum's to reach; the delete looks one up by name only
to clean up a create whose answer never came, and only with this
install's label. A workspace removed during the create records nothing,
and its new sandbox is deleted again.

By id, Daytona keeps answering for a deleted sandbox (deleting, then
destroyed), so those states count as gone (found by the live suite).

Cuts the reconciler's token refresh. ensureReady and the transport
repair already mint a fresh token when one is needed, and the
reconciler's mint ran outside the readiness join, so a release could
not wait for it (both open Codex P1s on ebe4781).
@ibetitsmike

Copy link
Copy Markdown
Contributor Author

Resuming the review loop with the recorded sandbox id (option C from #7100 (comment)), in 69803bc.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 69803bc98d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

🟢 e2e stories (Haiku): 14 passed

22 agent steps · 7 replayed from cache · 15 model calls · 46.2k tokens (24% cached)
Spend: $0.09 of the $1.00 cap (proxy ledger)

All 14 tests in 9 files
Test Agent Time
🟢 chatRestart.e2e.ts · 1 passed 1 step · 1 call 5.9s
🟢 a chat survives a restart of the server 1 step · 1 call 5.9s
🟢 compaction.e2e.ts · 1 passed 1 step · 1 call 4.5s
🟢 /compact replaces the old chat with a summary 1 step · 1 call 4.5s
🟢 pluginTrustRestart.e2e.ts · 1 passed 4 steps · 2 calls 9.2s
🟢 a trusted plugin hook stays trusted after the server restarts 4 steps · 2 calls 9.2s
🟢 settingsRoundTrip.e2e.ts · 1 passed 2 steps · 1 call 8.9s
🟢 a chat survives trips through Settings 2 steps · 1 call 8.9s
🟢 sidebarPerWorkspace.e2e.ts · 1 passed 4 steps · 1 call 4.2s
🟢 a new workspace starts with its right sidebar collapsed while another keeps it open 4 steps · 1 call 4.2s
🟢 smoke.e2e.ts · 1 passed 2 steps · 1 call 6.6s
🟢 the agent opens the playground workspace 2 steps · 1 call 6.6s
🟢 streamErrors.e2e.ts · 1 passed 1 step · 1 call 6.8s
🟢 stream errors show in the chat, and the chat recovers 1 step · 1 call 6.8s
🟢 todoNudge.e2e.ts · 1 passed 1 step · 1 call 2.8s
🟢 the agent finishes open TODO items after the turn-end nudge 1 step · 1 call 2.8s
🟢 touchAlignment.e2e.ts · 6 passed 6 steps · 6 calls 22.5s
🟢 the Settings close button is level with the title on a touch screen 390px wide 1 step · 1 call 4.0s
🟢 the Settings close button is level with the title on a touch screen 768px wide 1 step · 1 call 3.3s
🟢 Backup settings checkboxes are small and level with their labels on a touch phone 1 step · 1 call 3.9s
🟢 the heartbeat checkbox is small and level with its label on a touch phone 1 step · 1 call 2.9s
🟢 a reply's model and time stay on one line on a touch phone 1 step · 1 call 3.4s
🟢 the workspace actions menu stays on the title row on a touch phone 1 step · 1 call 5.1s

e2e 0.20.0 · 1m 15s · web · run artifacts

🟢 e2e Electron stories (Haiku): 3 passed

3 agent steps · 3 model calls · 8.0k tokens (22% cached)
Spend: $0.02 of the $1.00 cap (proxy ledger)

All 3 tests in 3 files
Test Agent Time
🟢 electron/deepLink.e2e.ts · 1 passed 1 step · 1 call 6.0s
🟢 a deep link of a second launch starts a chat in the running app 1 step · 1 call 6.0s
🟢 electron/menuBar.e2e.ts · 1 passed 1 step · 1 call 5.6s
🟢 the workspace menu bar is the desktop title bar 1 step · 1 call 5.6s
🟢 electron/smoke.e2e.ts · 1 passed 1 step · 1 call 4.6s
🟢 Xum Electron opens the playground workspace 1 step · 1 call 4.6s

e2e 0.20.0 · 18.2s · electron · run artifacts

🟢 e2e agent repros (Haiku): no tests selected

Spend: $0.00 of the $1.00 cap (proxy ledger)

e2e 0.20.0 · 5.3s · 2 targets (web, phone) · run artifacts

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 69803bc98d

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

The reconciler no longer refreshes tokens in the background, so the
token is replaced only when Xum brings the sandbox up (or an ssh command
fails). Say so, and that the user's own ssh clients can be refused after
the hour until Xum uses the sandbox again (UAT round 8 observation).
@ibetitsmike

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 8b8f113cf1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ibetitsmike

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: f0fec18553

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: f0fec18553

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

This branch was successfully deployed

1 active (outdated) deployment
staging - docs — 8b8f113c Deployed Oct 27, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant