Skip to content

feat(api): create the snapshot record at capture start and mark it error on failure#4999

Draft
mu-hashmi wants to merge 7 commits into
mainfrom
feat/snapshot-record-at-capture-start
Draft

feat(api): create the snapshot record at capture start and mark it error on failure#4999
mu-hashmi wants to merge 7 commits into
mainfrom
feat/snapshot-record-at-capture-start

Conversation

@mu-hashmi

@mu-hashmi mu-hashmi commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

TL;DR: snapshot-from-sandbox captures now create the snapshot record at accept time and mark it error (+errorReason) on every failure path, so failed captures are visible instead of silently vanishing.

Description

Capture previously inserted the Snapshot record only AFTER success. Any failure (error, timeout, transport abort) left zero evidence: sandbox state silently reverted, the SDK reported success, the snapshot 404'd forever (#4990).

After this PR:

  • The record is created pending when the capture is accepted (v0 and v2), so duplicate names 409 before any capture work runs.
  • Success transitions it to active (existing propagation preserved); failures (v0 background error, v2 FAILED job, cron timeout, initiation throw) mark it error with a real errorReason while the sandbox still reverts to its previous state.
  • v2 FAILED jobs settle pending snapshot quota via the pending -> error state event (closes a quota leak).
  • sizeGB is persisted for v0 records; deploy-window fallback keeps jobs from pre-rollout APIs working (legacy insert-ACTIVE).

Design decision: capture records reuse SnapshotState.PENDING, discriminated by isPendingCaptureSnapshot (pending + no buildInfo + empty imageName + empty ref), instead of a new enum value - avoids a Postgres ALTER TYPE migration and an OpenAPI enum change regenerating six client libraries. The !ref term exists because activation sets ref but never imageName, so reactivated rows (INACTIVE -> PENDING) would otherwise match the signature - found by review, fixed in 194a03a.

Behavior notes for reviewers:

  • A failed capture now holds the unique (org, name) with an error record until deleted - consistent with pull/build failures, but previously failures left no trace. Release-note worthy.
  • A v2 FAILED job arriving after the sandbox already left SNAPSHOTTING resolves via the cron timeout (generic reason) instead of the job's message - rare corner, accepted.

Review guide

Suggested order:

  1. apps/api/src/sandbox/utils/persist-snapshot-from-sandbox.util.ts - the record lifecycle helpers and the capture signature (risk center: the isPendingCaptureSnapshot predicate gates everything).
  2. apps/api/src/sandbox/services/sandbox.service.ts - accept-time insert + v0 failure path + initiation try/catch.
  3. apps/api/src/sandbox/services/job-state-handler.service.ts - v2 FAILED handling + quota settle.
  4. apps/api/src/sandbox/managers/snapshot.manager.ts - cron guard (risk center: interaction with the pull flow).
  5. Specs.

Generated files: one additive client regen documenting the 409 (response-map and doc entries across the go/java/python clients); guarded by the CI regen-drift gate.

Commit map

Commit What Why it exists
3e7f645 persist pending record at accept implementation (plan)
c189069 mark record from v2 job completion implementation (plan)
1421ab9 record lifecycle specs implementation (plan)
3465dae restore state + fail record when initiation throws Copilot review finding (valid)
194a03a exclude reactivated snapshots from capture guard cubic review P1 (valid)
4a0bf0e document the 409 duplicate-name response on the capture route review follow-up (spec completeness)
369e6df client regen for the 409 response generated

Related PRs

Part of #4990. Merge order: this PR first; #5004 (v0 async capture) is stacked on this branch and gets retargeted to main after merge; #5000 (SDK record polling) pairs with this and merges independently.

Validation

  • persist-snapshot-from-sandbox.util.spec.ts (21 assertions: insert fields/event/409 mapping, activation incl. legacy fallback and non-capture refusal, failure idempotency, signature predicate incl. reactivated rows) + new sandbox.service.spec.ts (3 tests on the initiation-throw path).
  • Full nx test api: 52 suites / 571 tests green; nx build api + lint green.
  • Spec: the 409 duplicate-name contract is annotated on the capture route and regenerated into the clients (additive-only: 12 insertions, 0 deletions, 4 files); regen built fresh with --skip-nx-cache and verified by the CI drift gate.
  • All CI checks green on 194a03a (incl. E2E).

Closes #4991

…cepted

Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
…pletion

Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
@nx-cloud

nx-cloud Bot commented Jun 11, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 369e6df

Command Status Duration Result
nx e2e:cleanup daytona-e2e ✅ Succeeded <1s View ↗
nx run-many --target=test:e2e --all --nxBail=true ✅ Succeeded 6m 25s View ↗
nx e2e daytona-e2e ✅ Succeeded 55s View ↗
nx run-many --target=build --projects=api,runne... ✅ Succeeded 7s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-06-12 01:14:55 UTC

@mu-hashmi mu-hashmi marked this pull request as ready for review June 11, 2026 03:32
Copilot AI review requested due to automatic review settings June 11, 2026 03:32

Copilot AI 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.

Pull request overview

This PR changes the snapshot-from-sandbox capture flow to create a Snapshot record immediately when the capture is accepted (state PENDING), then transition it to ACTIVE on success or ERROR (with errorReason) on failure/timeout. This aligns capture behavior with existing pull/build snapshot lifecycles so failures are observable and quota settlement follows the snapshot record’s state transitions.

Changes:

  • Added utilities to (1) create the pending capture record at accept-time, (2) activate it on success (or fall back to legacy insert), and (3) mark it errored on failure.
  • Updated v0 (background) and v2 (SNAPSHOT_SANDBOX job) paths to activate/fail the upfront record instead of only inserting after success.
  • Updated the snapshot state cron (check-snapshot-state) to treat capture-shaped PENDING snapshots specially and time them out to ERROR.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
apps/api/src/sandbox/utils/persist-snapshot-from-sandbox.util.ts Splits persistence into accept-time PENDING insert, success activation, and failure marking; adds capture-signature predicate.
apps/api/src/sandbox/utils/persist-snapshot-from-sandbox.util.spec.ts Adds unit coverage for pending insert, activation (incl. legacy fallback), failure marking, and capture predicate.
apps/api/src/sandbox/services/snapshot.service.ts Exposes new snapshot-from-sandbox lifecycle helpers via SnapshotService.
apps/api/src/sandbox/services/sandbox.service.ts Creates pending snapshot record before dispatching capture; v0/v2 failure paths mark record ERROR and remove legacy pending-usage rollback logic.
apps/api/src/sandbox/services/job-state-handler.service.ts On v2 job completion, activates pending record; on FAILED, marks record ERROR with job.errorMessage.
apps/api/src/sandbox/managers/snapshot.manager.ts Prevents pull/build pending-state handler from corrupting capture records; adds timeout-to-error behavior for capture-shaped pending snapshots.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// ACTIVE/ERROR), so the outer catch's rollback must not fire.
pendingSnapshotCountIncrement = undefined

const runnerAdapter = await this.runnerAdapterFactory.create(runner)

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 6 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread apps/api/src/sandbox/services/sandbox.service.ts
Comment thread apps/api/src/sandbox/managers/snapshot.manager.ts
…on throws

Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
Copilot AI review requested due to automatic review settings June 11, 2026 04:23

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>

@rajpratham1 rajpratham1 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.

This is a solid improvement to the snapshot lifecycle.

Creating the snapshot record at capture start gives users immediate visibility into pending work, surfaces duplicate-name conflicts before any runner work begins, and ensures failures/timeouts are recorded on the snapshot itself instead of silently disappearing. I also like that quota ownership is transferred to the snapshot lifecycle, with consistent settlement through the PENDING → ACTIVE/ERROR transitions.

The implementation covers both v0 and v2 runners, includes sensible fallback behavior for pre-existing jobs, restores sandbox state on initiation failures, and adds comprehensive regression tests for pending creation, activation, failure handling, and timeout scenarios.

I don't see any blocking issues. Looks good to merge.

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.

api: create the snapshot record at capture start and mark it error on failure

3 participants