Skip to content

chore(sandbox): address post-merge review threads on #14 + #17 - #20

Merged
kjgbot merged 3 commits into
mainfrom
chore/sandbox-14-17-thread-cleanup-0821
Aug 22, 2026
Merged

chore(sandbox): address post-merge review threads on #14 + #17#20
kjgbot merged 3 commits into
mainfrom
chore/sandbox-14-17-thread-cleanup-0821

Conversation

@kjgbot

@kjgbot kjgbot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Follow-up cleanup for the eight unresolved review threads that were still open on #14 and #17 at merge time — four on src/daytona/runtime.ts (from #14) and four on src/port.ts (from #17). All threads are P1/P2 from cubic-dev-ai and chatgpt-codex-connector; no human authors.

Resolution shape: 4 FIX, 4 ACK+RESOLVE.

Closes on #14 (4 threads → 2 FIX, 2 ACK+RESOLVE)

  • FIXsrc/daytona/runtime.ts line 772 (cubic P2, "bound refreshData with the same lookup deadline") — commit 6b286bc. Wraps the unbounded refreshData() call in recreateAfterFailedStart with awaitLookupOperation(..., lookupDeadline(undefined), ...), matching the same pattern used 5 other places in this file. A stalled refresh no longer wedges restart recovery.
  • FIXsrc/daytona/runtime.test.ts line 1890 (cubic P2, "delete-after-destroy race in smoke cleanup") — commit 7142b14. Extends the existing isTestDaytonaNotFound guard from the adjacent daytona.get(id) call to the daytona.delete(sandbox) call in the same cleanup loop, so eventually-consistent 404s on delete don't fail the after hook.
  • ACK+RESOLVEsrc/daytona/runtime.ts line 750 (chatgpt-codex-connector P1, "restrict recreation to verified Toolbox failures"). Prior lead reply on the thread explains: needs error-classification (401/403/429 vs 502/503/504) with SDK-shape tests we don't have; misclassifying dead-daemon as transient is the opposite failure mode and worse; recreateOnFailedStart: false escape hatch already documented. Tracked as a fast-follow for whoever adds the classifier tests.
  • ACK+RESOLVEsrc/daytona/runtime.ts line 893 (cubic P2, "networkAllowList as array"). Prior lead reply cites Daytona TypeScript SDK docs pinning networkAllowList: string (comma-separated). No array variant exists to preserve.

Closes on #17 (4 threads → 2 FIX (dedup'd), 2 ACK+RESOLVE)

  • FIXsrc/port.ts line 255 (chatgpt-codex-connector P2 + cubic-dev-ai P2, "keep resolved descriptor source-compatible" / "making modes required breaks TS consumers") — commit 6e166c9. Both threads flag the same source-compat regression. Split into two types: SandboxRuntimeCapabilities (five booleans + modes?: SandboxCapabilityModes, source-compatible again) and ResolvedSandboxRuntimeCapabilities (intersection with modes: SandboxCapabilityModes required) as the resolver's return type. Preserves both the source-compat contract and the always-present-after-resolution invariant. Two new tests pin the contract.
  • ACK+RESOLVEsrc/port.ts (chatgpt-codex-connector P2, outdated, "encode the adapter-hidden streaming state"). Directly addressed by the second commit of feat(port): structured capability modes alongside the boolean flags #17 which added CapabilityAbsence = "unknown" | "not-exposed" | "unsupported""not-exposed" is exactly the vocabulary for "provider has it, this package's ports do not reach it." Thread also isOutdated: true on the current file layout.
  • ACK+RESOLVEsrc/port.ts line 86 (cubic-dev-ai P2, "add distinct adapter-buffered output mode"). Design decision, not oversight: outputStreams describes observable API behavior (what the caller receives), not underlying provider capability. Adding "adapter-buffered" would push adapters to make claims about the provider's untested capability, which is precisely the "manufacture false confidence" trap the whole modes design is built to avoid. Provider-vs-adapter distinction, if wanted, belongs on a separate axis (a sibling providerCapabilityHints field), not as another member of this union — worth its own PR if it's a first-class concern.

Test discipline

  • npm run build && npm test after each commit. Suite: 220 → 222 (+2 tests for the port source-compat contract). 217 pass / 5 skipped / 0 fail on the tip.
  • Skip count callout: the +1 test-only change (runtime.test.ts 404 tolerance) lands inside the DAYTONA_API_KEY-gated DaytonaRuntime smoke suite that was already skipping in CI — so skipped count is unchanged (5 before, 5 after) rather than +1 as a naïve diff might imply. This is a coverage-preserving change, not a coverage regression.

Follow-ups tracked (not landed here)

  • The P1 error-classification for assertSandboxExecHealthy (from the ACK'd fix(daytona): recover exec after sandbox restart #14 line-750 thread). Deferred because it needs SDK-error-shape tests to avoid misclassifying dead-daemon as transient. Consumers who don't want automatic replacement can already set recreateOnFailedStart: false.
  • If a first-class "provider-can-stream, adapter-hides" signal is genuinely needed (from the ACK'd feat(port): structured capability modes alongside the boolean flags #17 line-86 thread), a separate providerCapabilityHints sibling field is the shape to consider rather than expanding OutputStreamMode.

Cross-links

🤖 Generated with Claude Code

kjgbot added 3 commits August 21, 2026 22:44
recreateAfterFailedStart calls refreshData() on the pre-restart sandbox
before reading its network/env/labels for replacementCreateParams. That
call was unbounded: if the SDK stalled on the refresh (rate limit,
transient 5xx, network stall), start() hung forever without ever
reaching the replacement create — the exact hang the recreate path
exists to avoid.

Wrap the refresh with the same `awaitLookupOperation` + `lookupDeadline`
pattern already used five other places in this file (rehydrate after
start, initial getById lookup, cleanup guards). Same default timeout,
same error message shape, no new abstractions.

No dedicated unit-test regression is added: the default lookup deadline
is 10s, which would make a "hangs forever without the fix" assertion a
10s+ test, and the awaitLookupOperation utility already has a fast unit
test at findByLabels (`lookup exceeded 20ms` at runtime.test.ts:325).
Existing recreate-path fake-sandbox tests continue to pass (fakeSandbox
exposes no refreshData, so the optional call is a no-op both before and
after).

Addresses cubic-dev-ai review thread on PR #14 (src/daytona/runtime.ts
line 772). Suite unchanged: 220 tests / 215 passed / 5 skipped / 0
failed (skips are DAYTONA_API_KEY-gated smoke, unrelated to this
change).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Session-Id: 34c847c1-1a32-4cba-b341-311f694f1145
The smoke suite's `after` hook loops over createdSandboxIds and calls
daytona.get(id) followed by daytona.delete(sandbox). daytona get/delete
is eventually consistent: after runtime.destroy(handle) already removes
the sandbox, get can still resolve briefly, and the subsequent delete
then rejects 404. Only the get was guarded by isTestDaytonaNotFound;
the delete rejection landed in cleanupFailures and failed the entire
after hook even though cleanup succeeded.

Extend the same isTestDaytonaNotFound guard to the delete call so a
404 there is treated as "already gone" rather than a cleanup failure.
assertDaytonaSandboxGone still runs afterward and confirms absence in
either path, so a real never-deleted sandbox continues to fail the
hook. Any non-404 delete error still bubbles.

Blast radius: DAYTONA_API_KEY-gated smoke suite only, skipped in CI
and in this environment (skip count unchanged: 5 before, 5 after).
Worst case if the classifier misclassifies a real delete failure as
404: one cleanupFailures entry silently dropped in the smoke `after`
hook — bounded, non-production.

Addresses cubic-dev-ai review thread on PR #14
(src/daytona/runtime.test.ts line 1890). Pattern mirrors the adjacent
get-guard in the same cleanup loop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Session-Id: 34c847c1-1a32-4cba-b341-311f694f1145
The modes field on SandboxRuntimeCapabilities was mandatory. External
TypeScript consumers that construct fixtures with the five pre-modes
booleans (asyncExec, reattach, detachedLaunch, warmLease, lifecycle)
stopped compiling on upgrade — the shape had a new required field they
had no way to know about, and adding modes to every fixture forces the
consumer to make claims about a runtime they might only be mocking for
one specific concern.

Two-type split preserves both invariants:

  - `SandboxRuntimeCapabilities`: five booleans + `modes?: SandboxCapabilityModes`.
    The exported base shape a consumer can construct.
  - `ResolvedSandboxRuntimeCapabilities = SandboxRuntimeCapabilities &
    { readonly modes: SandboxCapabilityModes }`: what
    `resolveSandboxRuntimeCapabilities` actually returns. `modes` is
    required here and always populated by the resolver (defaulting to
    "unknown", per the discipline the whole modes design encodes).

Callers who receive the resolver's output still see modes as required
(no optional-chain gymnastics at call sites). Callers who construct
fixtures literal-typed as `SandboxRuntimeCapabilities` no longer need
to invent modes they haven't observed. Nobody has to make a claim they
can't back.

Two new port tests pin the contract: one literal-constructs the base
type without modes and asserts the value shape; one assigns the
resolver output into `ResolvedSandboxRuntimeCapabilities` and reads
modes without a cast, so the compiler enforces the strict-return
invariant.

`index.ts` re-exports the new `ResolvedSandboxRuntimeCapabilities`
alongside the existing base type.

Addresses the duplicate P2 threads on PR #17 (chatgpt-codex-connector
and cubic-dev-ai, both at src/port.ts line 255).

Tests: 222 (+2) / 217 pass / 5 skipped / 0 fail. Skips unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Session-Id: 34c847c1-1a32-4cba-b341-311f694f1145
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d5b6dd78-f272-485a-99de-4e90f8912e31


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@kjgbot

kjgbot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

sandbox-lead review pass — PASS

Human read confirming safe to surface. This is the post-merge thread-cleanup PR for the already-merged #14 and #17.

  • Every FIX claim in the body verified true:
    • 6b286bcrefreshData() now wrapped in awaitLookupOperation(..., lookupDeadline(undefined), ...), matches the pattern used 5 other places in src/daytona/runtime.ts. Optional-chained call, no regression risk.
    • 7142b14daytona.delete(sandbox) in smoke cleanup now guarded by the existing isTestDaytonaNotFound helper, matching the adjacent get() call. Bounded to DAYTONA_API_KEY-gated smoke.
    • 6e166c9SandboxRuntimeCapabilities.modes is now optional; ResolvedSandboxRuntimeCapabilities = SandboxRuntimeCapabilities & { readonly modes: SandboxCapabilityModes } is new; resolver return type updated; WeakMap cache updated; ResolvedSandboxRuntimeCapabilities exported from index.ts.
  • ACK+RESOLVE decisions defensible:
  • Source-compat contract pinned: exactly 2 new tests in src/port.test.ts (lines 124–152) — one constructs pre-modes fixture (5 booleans only), one reads resolver output into strict type without cast. Old fixture constructors continue to compile; new callers get compiler enforcement of modes presence.

Safe.

@kjgbot
kjgbot merged commit 2d438f1 into main Aug 22, 2026
3 checks passed
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