Skip to content

Keep automations starting when Daytona's limits are reached - #349

Open
arseniycodes wants to merge 3 commits into
mainfrom
ash/daytona-quota-resilience
Open

arseniycodes wants to merge 3 commits into
mainfrom
ash/daytona-quota-resilience

Conversation

@arseniycodes

@arseniycodes arseniycodes commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Why

On 2026-10-16 from 12:00 UTC, automation runs in the EU cell failed with "Unable to create or clean up Daytona sandbox …". The real error was hidden:

DaytonaValidationError (400): Total disk limit exceeded. Maximum allowed: 2000GiB.

Stopped sandboxes count toward Daytona's disk limit. Every automation run without a native subscription keeps its sandbox stopped for 24 h. With the 10 GiB snapshot, about 200 runs a day filled the limit (196 stopped sandboxes, 1960 GiB). Archiving them by hand fixed it for now.

What changes

  • Paused sandboxes are archived after 30 minutes (setAutoArchiveInterval). Archived sandboxes do not count toward the disk limit and still resume, more slowly.
  • Quota rejections are retried, not failed. When Daytona rejects a creation with "… limit exceeded", nothing was created, so the worker:
    • for disk: archives the 20 longest-stopped responder-* sandboxes (stopped > 5 min);
    • waits 5 s, 15 s, 30 s, 60 s, 120 s (about 4 min) and retries the same name;
    • after that fails with DaytonaCapacityError, "Responder's sandbox capacity is temporarily full. Try again in a few minutes." This replaces Daytona's "upgrade your Tier" text.
  • Sweep every 10 minutes in the worker: archives sandboxes paused > 30 min (including ones set up before this change), deletes archived and errored responder-* sandboxes older than 24 h, and logs daytona_sandbox_sweep with disk, CPU and memory in use. Deleting does not rely on Daytona's auto-delete covering archived sandboxes.
  • Cleanup no longer hides the creation error. A sandbox that never appeared was already reported, so it no longer turns the failure into the generic AggregateError. The remaining aggregate message includes the creation error.

Tests

  • sandbox.test.ts: disk-full retry archives only long-stopped sandboxes and reuses the name without waiting for the rejected sandbox to appear; CPU-full waits the full budget, then throws DaytonaCapacityError; paused sandboxes get the archive interval and sandboxes deleted on stop do not; the sweep archives, deletes expired, and logs usage.
  • pnpm typecheck, eslint on changed files, and vitest run apps/worker (674 passed).

Follow-ups

  • CloudWatch alarm on daytona_sandbox_sweep.diskGiB (hosted repo).
  • Consider requeueing a run after DaytonaCapacityError instead of failing it.

Summary by cubic

Fixes automation runs failing when the organization's Daytona limits are reached, as happened in the EU cell on 2026-10-16 when stopped sandboxes filled the 2000 GiB disk limit.

  • Paused sandboxes are now archived after 30 minutes, so they stop counting toward the disk limit while still being resumable.
  • Only Daytona's total CPU, memory and disk limit rejections count as full capacity: the worker archives the longest-stopped responder-* sandboxes (disk only) and retries the same name for about 4 minutes before failing with a clear DaytonaCapacityError. Rate limits keep the normal delete-and-retry cleanup.
  • A sweep every 10 minutes archives sandboxes paused longer than 30 minutes, deletes archived, errored or build-failed responder-* sandboxes older than 24 hours, and logs daytona_sandbox_sweep with usage.
  • Cleanup failures for a sandbox that never appeared no longer replace the real creation error in the aggregate message.

Written for commit 8b02fd3. Summary will update on new commits.

View guided diff Turn on auto-fix

Daytona rejects new sandboxes once the organization reaches its disk, CPU
or memory limit. Stopped sandboxes count toward the 2000 GiB disk limit.
Each automation run keeps its 10 GiB sandbox stopped for a day, so about
200 runs a day filled the limit and every new run failed on 2026-10-16.

- Paused sandboxes are archived after 30 minutes. Archived sandboxes do
  not count toward the disk limit and still resume.
- When Daytona rejects a creation at a limit, the worker archives the
  longest-stopped sandboxes (disk) and retries the same name for about
  four minutes (CPU and memory) before failing with a clear message.
- A worker sweep every 10 minutes archives paused sandboxes, deletes
  archived and failed ones older than a day, and logs usage.
- A sandbox that never appeared during cleanup no longer replaces the
  creation error, and the remaining aggregate message includes it.
@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 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-16T14:45:44.163762Z 8b02fd3 Manual request
ℹ️ 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 Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04e7c5ec29

ℹ️ 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 apps/worker/src/sandbox.ts Outdated
Comment on lines +188 to +189
(sandbox.state === SandboxState.ARCHIVED || sandbox.state === SandboxState.ERROR) &&
Date.parse(sandbox.updatedAt ?? "") < expiredBefore

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 Delete build-failed sandboxes during the sweep

When a Responder sandbox remains in Daytona's distinct BUILD_FAILED terminal state past the retention window, this predicate never adds it to expired because it only recognizes ARCHIVED and ERROR. Such orphaned build failures therefore remain indefinitely and continue contributing their disk allocation, undermining the quota-recovery sweep; include SandboxState.BUILD_FAILED among the expired failure states.

Useful? React with 👍 / 👎.

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@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: 2f83ddd1a2

ℹ️ 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 +98 to +100
function isDaytonaQuotaError(error: unknown): boolean {
return error instanceof Error && /\blimit exceeded\b/i.test(error.message);
}

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 classify API rate limits as capacity

When Daytona returns a 429 whose message contains “rate limit exceeded,” this predicate also matches it. Because creator.create() includes post-create startup polling, that rate-limit error can occur after the sandbox exists; on the final retry or an abort, the quota branch calls failCreation without adding the current name to abandoned, leaving that sandbox undeleted and reporting a misleading DaytonaCapacityError. Restrict this check to the known total CPU, memory, and disk quota messages, or explicitly exclude provider errors with status 429 so they retain the normal cleanup path.

Useful? React with 👍 / 👎.

@arseniycodes

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 8b02fd331a

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

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