Skip to content

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

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

ibetitsmike wants to merge 162 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 (or attaches to) 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. This is Phase 1b of the "move agent compute off the Xum host" plan; Phase 0 (cgroup priority) and Phase 2 (snapshots, node-side project defaults, resize) are separate follow-up PRs.

Background

Default runtimes run every agent command on the Xum host, so builds and test suites compete with the server and the agent loop. The ssh runtime already moves all of that to a remote box (Coder is ssh plus a coder sub-config), but no provider hands out a fresh, sized box in seconds. Building on SSHRuntime reuses exec, file ops, bundle sync, worktrees, fork, PTY, background jobs, and the AI proxy forward, plus every check keyed on ssh, instead of adding a new runtime type.

What the user gets

  • Settings > Runtimes, Daytona row (global scope): API key (write-only; shows whether a key is set and whether it comes from settings or DAYTONA_API_KEY), API URL (for BYOC), target region, Test connection, Remove key.
  • Settings > Runtimes, Daytona row (project scope): default snapshot and size preset (Small 2/4/10, Medium 4/8/20, Large 8/16/40 vCPU/GiB/GiB disk).
  • New workspace: a Daytona runtime button with New/Existing, snapshot, and size; prefilled from the project default. Existing lists the organization's sandboxes.
  • Runtime badge: sandbox state (starting, started, stopped, archived, error).
  • Docs: docs/runtime/daytona.mdx; also fixes the remote user-hook path in docs/hooks/tools.mdx (~/.mux, not ~/.xum).

Implementation

Module Role
src/node/config/daytonaCredentialsStore.ts <xum home>/daytona.json, mode 0600, atomic write. The key never reaches the frontend, runtime config, providers.jsonc, or secrets.json (which is injected into agent turns and terminals).
src/node/services/daytona/daytonaClient.ts Thin typed fetch client with per-call timeouts. @daytona/sdk was rejected: 2 MB unpacked and it pulls the AWS S3 client, OpenTelemetry, axios, and socket.io.
src/node/services/daytona/daytonaService.ts Availability, labels (xum.instance, xum.workspaceId, xum.project), token cache with refresh before expiry and revoke on replace/delete/settings change, start-and-wait with state mapping.
src/node/runtime/DaytonaSSHRuntime.ts Extends SSHRuntime and mirrors CoderSSHRuntime: create (snapshot or image, 30 min auto-stop, 1 day auto-archive, no auto-delete), ensureReady (start stopped/archived sandboxes, 60 s cache), one-shot recovery for a refused token, fork as shared, delete with dirty/unpushed checks (starting a stopped sandbox to run them) and last-reference deletion.
src/node/runtime/daytonaSshConfig.ts <xum home>/ssh/daytona.conf (0600) with one Host <id>.xum--daytona block per sandbox; the token is the SSH user, so it stays out of argv and persisted config. Included from a managed Host *.xum--daytona block in ~/.ssh/config; an ssh -G check reports a user Host * User line that would shadow the token.
Host-key pinning The gateway host and its host keys come from GET {apiUrl}/config over TLS into daytona_known_hosts, with StrictHostKeyChecking yes. Every OpenSSH call for a sandbox also passes -o StrictHostKeyChecking=yes, so a user Host * entry above Xum's block cannot weaken it, and the ssh -G check reports a shadowed User or UserKnownHostsFile. The SSH2 transport enforces the same pin, including under xum server. sshConfigParser.ts now follows Include, gated by the enclosing Host block as OpenSSH does.
src/node/runtime/daytonaTokenRedaction.ts Replaces tokens with <daytona-token> in SSH errors, logs, and terminal output.
src/node/services/daytona/daytonaReconciler.ts 60 s sweep: keepalive for started sandboxes with streaming turns, running background jobs, or detached jobs that survived a Xum crash (Daytona counts only SSH/API traffic as activity), and stop (never delete) labeled sandboxes no workspace references.
src/node/runtime/daytonaAgentHomeSeed.ts Copies the host's tool_env, tool_pre, tool_post, and global skills (directories with a valid skill name and a SKILL.md) into a created sandbox's agent home, so global hooks run there. Follows a link inside a skill only while it stays in that skill. Never touches attached sandboxes.
Wiring runtimeFactory.ts selects DaytonaSSHRuntime; sshForwardTarget.ts skips restore at startup (no billed starts); archive reuses the remote-box setting (keep, stop, or delete), falling back to stop when the delete path finds unsaved work; UI status checks (git, PR) never start a sandbox.

Validation

  • Phase 1a spike: the stored Daytona API key returns HTTP 401, so the real-gateway checks did not run. The design follows the public OpenAPI (/api-json, server v0.225.2) and the public GET /api/config, and prior art that uses the same gateway for exec, ControlMaster, and git over SSH.
  • Remote UAT on a dogfood Coder workspace, against a fake Daytona control plane plus a local sshd as the gateway (6 rounds: 2 FAIL, then PASS at da988ae, ce322cf, and a full smoke round after the cleanup commit a873c96; a targeted round on the review fixes at fddee2d passed every criterion (terminals start a stopped sandbox, seed symlink and size limits, draft keeps Daytona choices, attached sandboxes never started by the sweep, crash-surviving jobs kept alive) but found a stale job record keeping a sandbox alive forever, fixed in ea0a98e; the gateway refresh passed at 40016c5). Covered: fake-key Test connection errors, masked key field, no secrets in ps or attachments, create with size and labels, file/git in the sandbox, include file mode 0600 and no token in persisted config, sub-agent fork into the same sandbox, out-of-band stop then next turn restarts, keepalive and orphan stop, archive stop, dirty delete refused, clean delete removes the sandbox, existing-sandbox attach, concurrent and failure shapes (stopping, starting, GET 500, hung GET, out-of-band delete and token revoke), and phone-width (375 and 390 px) and 1440 px screenshots. Every round ended with 0 leftover sandboxes and 0 live tokens beyond the deliberate orphan fixture.
  • Later UAT rounds on the Codex review fixes: PASS at 33d48fa (settings busy refusal, pooled ssh retirement, plain-http URL rule, seed budget, removal labels and tokens), and a final regression smoke PASS at 17ac602 (create, exec, turn, archive, unarchive, delete; owner removal drops the xum.instance label through PUT /sandbox/{id}/labels and no sweep stops the handed-off sandbox; a failed relabel refuses a normal removal; releasing access stops the ControlMaster and forward; forced removal on a stopped sandbox names the checkout left on it; missing and Windows-default snapshots refused before any create; 0 live tokens at the end). Its one issue, a forced removal hiding a failed relabel, is fixed in bf08d89.
  • Test audit: each of the 87 added or changed unit test cases was run against a deliberate break of the behavior it claims to cover; 85 failed as expected, and the 2 that did not (an un-awaited rejects, a dedupe the fake control plane hid) were fixed.
  • tests/runtime/daytonaRuntime.test.ts runs against real Daytona only with TEST_DAYTONA=1; it is not in the unit suite.

Risks

  • Shared SSH code (medium). SSHRuntime.ts, SSH2ConnectionPool.ts, sshConnectionPool.ts, sshConfigParser.ts (Include support), muxSshConfigWriter.ts, and the OpenSSH/SSH2 transports gained hooks for host-key pinning, token refresh, and refused-auth recovery. Plain SSH and Coder workspaces go through these paths. The new host-key, redaction, and token hooks act only on Daytona aliases; the delete safety check moved into an overridable checkWorkspaceDeletable with the same script. Two changes apply to every host: the ssh2 config parser now follows Include (up to 16 levels; an Include under a Host block applies only when that block matches, under Match only Match all expands, and the includer's scope is restored after it), and writing Xum's managed blocks (Coder and Daytona) into ~/.ssh/config now also takes a cross-process lock file next to the resolved config.
  • Real gateway unverified (medium, Daytona only). Behavior against the real gateway (exit 255 semantics, ControlMaster, -R forwards for the AI proxy, -A) is untested until a valid key is available. Everything is behind an explicit runtime choice.
  • Settings changes wait for idle Daytona work. Saving a new API URL, key or target is refused while a Daytona operation runs (create and setup, delete, start and token mint, archive, the 60 s sweep); the form says so and a retry succeeds once it ends. This makes every "operation spans an account change" race impossible by construction. A change also retires pooled ssh connections (ControlMaster and ssh2) to the old gateway, and a plain-http API URL is refused unless its host is loopback.
  • Billing. Sandboxes are billed while running. Auto-stop (30 min), the reconciler's orphan stop, and archive stopping by default bound the exposure; Xum deletes a sandbox only when the user deletes its last workspace (or archives with the delete setting), and never one a workspace attached: when the owner goes first and only attached workspaces remain, Xum drops its install label (so its orphan sweep never stops the sandbox) and the removal warns that the user now owns it.

Follow-ups

  • Run the Phase 1a real-gateway checks and a real-Daytona UAT round once a valid API key is available.
  • A token revoked out of band while the sandbox is stopped takes up to three commands to recover (recovery is one-shot per exec).
  • A turn sent within the 60 s ready cache after an out-of-band stop reports runtime_start_failed although its bash runs after the restart (startup-file load runs before the runtime restarts the sandbox).
  • A terminal that cannot start its sandbox, or meets a wrong gateway host key, shows only "Internal Server Error"; the real reason is in the log.
  • A self -> . symlink loop inside a skill is copied until the seed's depth limit (8 levels).
  • On main, a leftover empty background-job record dir (seen after a display-name collision on a shared box) blocks archive and remove for SSH-based workspaces; this PR only stops it from keeping a Daytona sandbox alive.
  • The runtime badge can stay stale after an out-of-band stop (including Daytona's idle auto-stop) until the window regains focus. Follow-up: poll displayed Daytona statuses at a low rate.
  • A Daytona workspace refuses a Xum root or home path with a character that ssh does not read literally in config paths (anything but letters, digits, space, ._-+@,=~ and path separators), with an error that names it.
  • Medium and Large presets exceed Daytona's standard-tier limits (4 vCPU, 8 GiB, 10 GiB disk); on such an organization, setup fails with Daytona's limit error and the created sandbox is deleted. Follow-up: offer only the presets the organization's limits allow.
  • With two backends on one Xum root, archiving two workspaces that share one sandbox at the same moment can skip the archive policy in both (each sees the other as active). Auto-stop still stops the sandbox; a delete policy applies when a workspace is removed. Follow-up: serialize archive decisions per sandbox.
  • With two backends on one Xum root, a detached job that the other backend started and then left running (it exited or crashed) does not keep the sandbox alive, because this backend probes over ssh only for jobs that survived its own restart. Re-probing would itself count as activity and defeat auto-stop.
  • If a failed setup also cannot delete its sandbox and the setup error persists, only a forced removal of that workspace deletes the sandbox.
  • A normal (not forced) removal of an attached workspace on a stopped sandbox starts the sandbox for the dirty check and leaves it running until auto-stop.
  • Removal notices name the sandbox by its stored string (name or id) and show the checkout path unexpanded (~/...).
  • The Windows-default snapshot check reads the cached gateway config, so a default changed on the deployment is seen only after the cache refreshes or a settings save.
  • Two backends on one Xum root (desktop app next to xum server) that remove the last two workspaces of one owned sandbox at the same time both keep it; the orphan sweep stops it but nothing deletes it. Deleting needs the in-memory "removal passed its checks" record persisted where both backends can read it.
  • Phase 0 (server cgroup priority) and Phase 2 (snapshots, node-side project defaults, resize) as separate PRs.

…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 22, 2026, 2:31 AM

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T03:22:52.182682Z 69eb986 New commits
🔒 Security Review ✅ Completed 2026-10-22T03:20:26.468641Z 69eb986 New commits

Security findings

Advisory findings (4)

ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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
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: 3a991bd601

ℹ️ 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
Comment thread src/node/services/daytona/daytonaService.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: b805476db8

ℹ️ 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
Comment thread src/node/services/daytona/daytonaService.ts Outdated
… replaced

When another backend sharing the root wrote a sandbox's first include
block after this process's startup load, ensureSshAccess minted a
replacement with no previous token, so the other backend's token stayed
live until it expired. It is now held as due at once for the sandbox
this process is about to use, so the mint replaces and revokes it. The
other backend's next command is refused and goes through the
refused-token path, which mints its own.

@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: 10d01df2bb

ℹ️ 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
…s: the current setting

The archive hook's attached-last path also reads the setting when the last use
ends, so removal and archive stay consistent; the docs say so.

@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: 8524ac758e

ℹ️ 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/config/daytonaCredentialsStore.ts Outdated
Comment thread src/node/services/daytona/daytonaService.ts
Comment thread src/browser/hooks/useDaytonaResources.ts
Comment thread src/node/runtime/sshConfigParser.ts
Comment thread src/node/services/bashAiProxy/reverseForwards.ts

@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: ecebbfd6b5

ℹ️ 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

@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: ecebbfd6b5

ℹ️ 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
Comment thread src/node/runtime/DaytonaSSHRuntime.ts
An editor's Remote-SSH reads ~/.ssh/config without Xum's -F, and OpenSSH
keeps the first value it reads, so an earlier user `Host *` that turns off
host key checking, forwards the agent or sets a proxy applied to sandbox
aliases. The Daytona managed block now goes on the first line (an appended
block from an earlier Xum moves there), ends with a `Host *` scope reset so
the user's following top-level lines still apply to every host, and each
alias sets ProxyCommand none, ForwardAgent no, ForwardX11 no and
PermitLocalCommand no. KnownHostsCommand is left alone: it needs OpenSSH
8.5 or an IgnoreUnknown that would replace the user's own.

@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: 4040f70bea

ℹ️ 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/daytonaSshConfig.ts
Comment thread src/browser/utils/openInEditor.ts
Comment thread src/browser/hooks/useDaytonaResources.ts Outdated
Comment thread src/node/services/daytona/daytonaService.ts
…ection is live

While Xum reconnects, useAPI() can keep the last client, whose calls fail
at once with ConnectionLostError, and the same object may come back. The
creation controls' resources and the Settings access form loaded once per
client, so a load that failed in the gap stayed failed. Both now load on
useLiveAPI(): nothing runs while reconnecting, and the load runs again
when the connection is live. The access form also clears its load error
after a load that succeeds.

@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: 69eb98671d

ℹ️ 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 on lines +271 to +273
const ready = await args.api.workspace.ensureDaytonaSandboxReady({
workspaceId: args.workspaceId,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Admit editor use before starting the Daytona sandbox

When an archive races an editor click, this await starts or refreshes Daytona before recordEditorOpen acquires the editor workspace-use lease or checks archivingWorkspaces. For example, a stop archive can observe an already-stopped sandbox and decide no stop is needed after this endpoint has read the unarchived workspace but before it starts the sandbox; the subsequent recording is refused once the archive is durable, yet the archived sandbox remains running and billable. Native-terminal opening avoids this by holding its use lease before ensureDaytonaSandboxUp; move Daytona preparation into the same admitted backend operation as editor recording.

AGENTS.md reference: AGENTS.md:L170-L170

Useful? React with 👍 / 👎.

filePath: this.filePath,
error: getErrorMessage(error),
});
return {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not switch accounts after credential read errors

When daytona.json selects account A but reading it transiently fails due to a permission or I/O error, and the process also has DAYTONA_API_KEY/DAYTONA_API_URL for account B, returning {} makes resolve() silently choose B. The strict read added for applyUpdate() does not cover this runtime path, so the next account lease can pin B and run create, start, or delete operations there even though the saved settings still select A. Propagate non-ENOENT read failures instead of treating them as absent credentials.

Useful? React with 👍 / 👎.

log.info("Daytona reconcile: stopping an unreferenced sandbox", { sandboxId: sandbox.id });
try {
await this.call(
this.options.daytonaService.stopSandbox(sandbox.id),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Settle owned sandboxes after shared-child removals

When a Daytona parent is removed while an active taskIsolation: "none" child borrows its checkout, WorkspaceService skips the parent's runtime deletion to preserve the child; removing that child later also skips runtime deletion because it owns no checkout. Once the child record disappears, this orphan path only stops the sandbox, and because auto-delete was disabled at creation no remaining workspace can ever call deleteSandbox, leaving the Xum-owned sandbox's billed storage indefinitely. This is independent of the known two-backend last-reference race because both skips occur in the normal single-backend removal flow; defer ownership cleanup until the borrower ends or explicitly settle the sandbox when the last borrowed reference is removed.

Useful? React with 👍 / 👎.

This branch was successfully deployed

1 active (outdated) deployment
staging - docs — 4040f70b Deployed Oct 22, 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