Skip to content

feat(box): image pipeline + single box identity (drop dual boxId)#756

Closed
law-chain-hot wants to merge 1 commit into
mainfrom
feat/box-image-id-clean
Closed

feat(box): image pipeline + single box identity (drop dual boxId)#756
law-chain-hot wants to merge 1 commit into
mainfrom
feat/box-image-id-clean

Conversation

@law-chain-hot

@law-chain-hot law-chain-hot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Backend image + identity refactor against main. No dashboard cloudBox migration, no TS SDK package removal — those stay in #748.

  1. Image pipelineimage is a first-class Box column; the full OCI ref flows end-to-end (request → PG → CREATE_BOX payload → runtime.Create) with no curated-key⇄ref translation. A thin allowlist gate validates at the request boundary only. Runner contract field is image. e2e adapted.
  2. Single box identity — collapse the dual uuid/boxId into one 12-char base62 id (the PK). The internal engine id is never exposed; boxId (the redundant duplicate of the cloud id) is dropped everywhere.
  3. Regenerate the 4 API clients (CI-faithful, run in a BoxLite box): Box/AdminBoxItem/Workspace lose boxId, gain image.
  4. Consumers of the dropped boxId fixed in place (this is NOT the SDK removal): dashboard reads switch boxIdid (6 files); sdk-typescript's Box mirrors the new api-client shape (drop boxId field, add image). The SDK package is untouched.
id model:   uuid + boxId   ──►   one 12-char base62 id (cloud id)
                                  engine mints its own internal id, never exposed
image:      key/translation ──►   full OCI ref, single representation, no translation

Scope boundary (what's deferred to #748)

Verification

  • sdk-typescript build OK · dashboard tsc --noEmit clean · API jest 112/112 · go vet clean (runner pkg) · engine unchanged vs main.
  • API clients regenerated inside a BoxLite box (CI-faithful).

Deployment

Baseline migration edited in place (box PK = varchar(12), image column added, boxId dropped) → requires a DB rebuild (agreed; nothing launched).

Summary by CodeRabbit

Release Notes

  • New Features

    • Added OCI image reference support for box creation with a curated allowlist of supported images.
    • Simplified box identity to use a single unified identifier.
  • Breaking Changes

    • Updated Box schema: removed boxId field, added required image field.
    • Updated Workspace schema: removed boxId, made image required.
    • Updated AdminBoxItem schema: organizationId is now optional.

Backend-only image/identity refactor against main — no dashboard cloudBox
migration, no TS SDK package removal (those stay in #748).

- Image: first-class `Box.image` column, full OCI ref end-to-end (no
  curated-key translation), runner contract field `image`, e2e adapted.
- Single identity: collapse the dual uuid/boxId into one 12-char base62
  id (the PK). The internal engine id is never exposed; `boxId` (the
  redundant duplicate of the cloud id) is dropped everywhere.
- Regenerate the 4 API clients (CI-faithful): Box/AdminBoxItem/Workspace
  lose boxId, gain image.
- Consumers of the dropped boxId fixed in place (NOT the SDK removal):
  dashboard reads switch boxId->id (6 files); sdk-typescript Box mirrors
  the new api-client shape (drop boxId field, add image). The SDK package
  itself is untouched — its removal + the cloudBox dashboard migration
  remain #748.

Verified: SDK build OK, dashboard tsc clean, API jest 112/112, go vet
clean (runner pkg), engine unchanged vs main.
@law-chain-hot law-chain-hot requested a review from a team as a code owner June 13, 2026 10:15
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR consolidates box identity from a dual-field model (internal id UUID + public boxId) into a single 12-character base62 id, removes the boxId field throughout the codebase, adds a required OCI image field to track boot image references, and enforces image allowlisting at the request boundary via environment-configurable supported refs.

Changes

Box Identity Refactor and Image Allowlisting

Layer / File(s) Summary
OpenAPI Schema Contract and Generated Client Models
apps/api-client-go/api/openapi.yaml, apps/api-client-go/model_*.go, apps/libs/api-client/src/models/*.ts
OpenAPI definitions remove boxId from Box, Workspace, and AdminBoxItem; add required image fields. Auto-generated Go and TypeScript client models (Box, Workspace, AdminBoxItem) are regenerated to match the new schema contract with updated field types and JSON mappings.
Core Box Entity and DTO Model Changes
apps/api/src/box/entities/box.entity.ts, apps/api/src/box/dto/box.dto.ts, apps/api/src/box/dto/create-box.dto.ts, apps/api/src/box/dto/workspace.deprecated.dto.ts
Box entity replaces UUID id with character-varying(12) and adds image column. BoxDto removes boxId, adds image with Swagger docs. CreateBoxDto adds optional image field with validation. Entity and DTO tests updated to reflect single-id identity model.
Image Allowlisting and Validation
apps/api/src/box/constants/curated-images.constant.ts, apps/api/src/box/constants/curated-images.constant.spec.ts, apps/api/src/box/services/box.service.ts, apps/api/src/box/services/box.service.warm-pool.spec.ts
New module introduces pinned OCI image allowlist with environment-variable overrides. assertSupportedImage() validates requests against allowlist, defaulting to base ref or throwing BadRequestError. Warm-pool and regular box creation paths enforce allowlisting; tests verify allowlist behavior and env override preference.
Box Lookup Semantics and Cache Invalidation
apps/api/src/box/utils/box-lookup-cache.util.ts, apps/api/src/box/services/box-lookup-cache-invalidation.service.ts, apps/api/src/box/services/box.service.ts, apps/api/src/box/services/box.service.box-id.spec.ts
Cache key helpers renamed from *ByBoxId to *ById. Box service methods (findOneByIdOrName, getOrganizationId, getRunnerId, getRegionId) now exclusively query by Box.id. Cache invalidation service removes boxId/previousBoxId from parameters and uses id for key generation and logging.
Database Migration and Repository Cache Updates
apps/api/src/migrations/1741087887225-migration.ts, apps/api/src/box/repositories/box.repository.ts
Migration removes boxId column and redefines id as character varying(12). Repository cache invalidation payloads remove boxId field; invalidateLookupCacheOnUpdate narrows previousBox type to essential fields only.
Runner Adapter and createBox Pipeline
apps/api/src/box/runner-adapter/runnerAdapter.ts, apps/api/src/box/runner-adapter/runnerAdapter.v0.ts, apps/api/src/box/runner-adapter/runnerAdapter.v2.ts, apps/api/src/box/runner-adapter/runnerAdapter.v2.createBox.spec.ts, apps/api/src/box/managers/box-actions/box-start.action.ts
RunnerAdapter interface adds createBox(box) method. V0 adapter rejects creation; V2 builds CREATE_BOX job payload with id, image, quotas, and environment. BoxStartAction transitions UNKNOWN-state boxes to CREATING via createBox. Tests verify job enqueueing and payload field mapping.
Admin Observability and Overview Service Updates
apps/api/src/admin/services/overview.service.ts, apps/api/src/admin/services/observability.service.ts, apps/api/src/admin/services/observability.service.spec.ts, apps/api/src/admin/dto/admin-overview.dto.ts
Admin overview maps AdminBoxItemDto with organizationId instead of boxId. Observability service uses box.id for correlation and matching instead of box.boxId fallback. Tests consolidate box identity and remove alternate box variant expectations.
Dashboard and Frontend Identity Updates
apps/dashboard/src/lib/box-identity.ts, apps/dashboard/src/components/BoxTable/BoxTableActions.tsx, apps/dashboard/src/components/admin/AdminPeopleBoxesView.tsx, apps/dashboard/src/components/admin/adminDiagnoseTarget.ts, apps/dashboard/src/components/admin/adminHelpers.ts, apps/dashboard/src/hooks/useBoxWsSync.ts
Box identity helpers (getBoxPublicId, getBoxRouteId) operate on id only. UI components remove box.boxId references; cache sync and search filters use id exclusively. UUID-like display name filtering is removed.
REST API Mapping and Boxlite Integration
apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts, apps/api/src/boxlite-rest/mappers/box-to-box.mapper.spec.ts, apps/runner/pkg/api/dto/box.go, apps/runner/pkg/boxlite/client.go, apps/runner/pkg/boxlite/stubs.go
BoxLite REST mapper derives box_id from box.id and sets image from box.image. CreateBoxDTO removes boxId and ArtifactRef; adds required image field. Runner client uses image instead of artifactRef. Tests verify image and id field threading through REST/runner integration.
End-to-End Test Infrastructure and Fixtures
scripts/test/e2e/bootstrap.sh, scripts/test/e2e/fixture_setup.py, scripts/test/e2e/cases/conftest.py, scripts/test/e2e/cases/test_*.py, scripts/test/e2e/sdks/*.{c,go,ts}, scripts/test/e2e/README.md
E2E bootstrap configures supported-image allowlist env vars. Fixture setup removes snapshot registration, derives default image from API env or profile, writes image to credentials. conftest adds _default_image() resolver. Test cases replace UUID regex with BOX_ID_RE (12-char base62) and use DEFAULT_IMAGE for requests. SDK smoke tests default to pinned Boxlite agent image.
TypeScript SDK and API Documentation Updates
apps/libs/sdk-typescript/src/Box.ts, apps/libs/api-client/src/docs/*.md, apps/libs/api-client/src/models/*.ts
SDK Box class removes boxId, adds image. processBoxDto sets image instead of boxId. Auto-generated API docs for Box, Workspace, AdminBoxItem updated: boxId removed, image added with description, field examples revised.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • boxlite-ai/boxlite#710: Updates e2e CLI detach recovery tests to parse new 12-char base62 box id format and use shared default image; directly modifies same test files introduced in this PR.
  • boxlite-ai/boxlite#678: Updates e2e suite (bootstrap.sh, cases/conftest.py, SDK test parsing) to match new box identity/image contract; builds on same test harness modified in this PR.
  • boxlite-ai/boxlite#720: Modifies scripts/test/e2e/fixture_setup.py quota SQL admin-user selection; overlaps with this PR's updates to the same file's user-targeting logic.
  • boxlite-ai/boxlite#743: Deletes apps/libs/sdk-typescript/src/Box.ts as part of removing @boxlite-ai/sdk package; directly overlaps this PR's boxId removal and image addition to the same module.
  • boxlite-ai/boxlite#683: Adds error-code-mapping test suite; this PR updates same test file's image field references to use conftest.DEFAULT_IMAGE.

Suggested reviewers

  • DorianZheng
  • G4614

🐰 Boxes shed their UUID coats, now wearing base62 charm,
Images curated, allowed, and close to arm,
Tests refit for nine, identities made anew—
One ID to find them all, in every queue! 🎯

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/box-image-id-clean
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/box-image-id-clean

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/api-client-go/api/openapi.yaml (2)

10216-10321: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Expose image on CreateBox.

The PR moves image selection into the request contract, but CreateBox still has no image field at all. Generated clients therefore can't submit the OCI ref that the backend now persists and forwards to CREATE_BOX.

Suggested schema shape
     env:
       additionalProperties:
         type: string
       description: Environment variables for the box
       example:
         NODE_ENV: production
       type: object
+    image:
+      description: The OCI image ref the box boots from
+      example: ghcr.io/boxlite-ai/boxlite-agent-python@sha256:80d562a57f4bc12def4e54dbdb9e7d26d3268fe0767a2955ab5ad718041145d6
+      type: string
     labels:
       additionalProperties:
         type: string

Mirror the required/optional status from CreateBoxDto when regenerating the clients.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api-client-go/api/openapi.yaml` around lines 10216 - 10321, The
CreateBox schema in openapi.yaml is missing the image property used by the
backend; add an "image" property to the CreateBox object (matching the
required/optional status from the server-side CreateBoxDto), with type string
and an example containing an OCI ref, and then regenerate clients so they can
submit the OCI image ref that the backend expects; update the CreateBox schema
block and ensure the volumes/items and other existing fields remain unchanged.

9938-9979: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The public examples/docs still advertise the removed UUID-based identity model. This rollout switches public box/workspace ids to the single exposed id, but these schemas/examples still describe or show UUID/internal ids.

  • apps/api-client-go/api/openapi.yaml#L9938-L9979: update the Box.id description/example to the new public id semantics.
  • apps/api-client-go/api/openapi.yaml#L10121-L10196: change the paginated box example items to use the new public id shape.
  • apps/api-client-go/api/openapi.yaml#L12487-L12529: update the Workspace.id description/example to the new public id semantics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api-client-go/api/openapi.yaml` around lines 9938 - 9979, Update the
OpenAPI examples and descriptions to use the new public id semantics instead of
UUID/internal ids: in apps/api-client-go/api/openapi.yaml (lines 9938-9979)
change the Box.id description to state it's the public exposed ID (not an
internal UUID) and replace the example value (fd955d93-...) with a
representative public id (e.g., "box_abc123" or the project's public id format)
and update any adjacent example fields that reference the internal UUID; in
apps/api-client-go/api/openapi.yaml (lines 10121-10196) update the paginated
boxes example items so each item uses the public id shape/value (replace UUID
examples with the public id format) and adjust any descriptive text that
mentions "internal UUID"; and in apps/api-client-go/api/openapi.yaml (lines
12487-12529) change the Workspace.id description/example to the new public id
semantics and swap the UUID example for a representative public workspace id
(e.g., "workspace_xyz789"). Ensure the schema names Box.id and Workspace.id are
the ones updated so examples and generated docs reflect the public id model
consistently.
apps/api/src/box/dto/box.dto.ts (1)

38-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update id schema metadata to the new single-ID format.

The id field is now the public 12-character base62 identifier, but the Swagger description/example still describe a UUID. Please update this metadata so published docs and generated examples match the new contract.

Suggested update
   `@ApiProperty`({
-    description: 'The internal UUID of the box',
-    example: 'fd955d93-e74a-48e7-9f2d-fcbe6dd9e920',
+    description: 'The public 12-character base62 ID of the box',
+    example: 'aB3dE6fGh1Jk',
   })
   id: string
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/box/dto/box.dto.ts` around lines 38 - 41, Update the
`@ApiProperty` metadata for the id field in the Box DTO to reflect the new public
12-character base62 identifier (not a UUID): change the description to mention
"public 12-character base62 ID" and replace the UUID example with a
representative 12-char base62 example (e.g. "A1b2C3d4E5f6") so Swagger docs and
generated examples match the new contract; locate the `@ApiProperty` decorator
applied to the id property in the DTO (box.dto.ts) and edit its description and
example accordingly.
apps/api/src/box/repositories/box.repository.ts (1)

84-87: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce immutable id in repository updates.

The update paths still accept id inside updateData, but cache invalidation now assumes a stable ID. If id is mutated, stale lookup entries and inconsistent related records are possible. Reject or strip id in both update and updateWhere.

Suggested hardening
   async update(id: string, params: { updateData: Partial<Box>; entity?: Box }, raw = false): Promise<Box | void> {
     const { updateData, entity } = params
+    if (updateData.id !== undefined && updateData.id !== id) {
+      throw new BoxConflictError()
+    }
+    const { id: _ignoredId, ...safeUpdateData } = updateData

     if (raw) {
-      await this.repository.update(id, updateData)
+      await this.repository.update(id, safeUpdateData)
       return
     }

     const box = entity ?? (await this.findOneBy({ id }))
@@
-    Object.assign(box, updateData)
+    Object.assign(box, safeUpdateData)
@@
-        { ...updateData, ...invariantChanges },
+        { ...safeUpdateData, ...invariantChanges },
       )
@@
   async updateWhere(
@@
-    const { updateData, whereCondition } = params
+    const { updateData, whereCondition } = params
+    if (updateData.id !== undefined && updateData.id !== id) {
+      throw new BoxConflictError()
+    }
+    const { id: _ignoredId, ...safeUpdateData } = updateData
@@
-      Object.assign(box, updateData)
+      Object.assign(box, safeUpdateData)
@@
-      await entityManager.update(Box, id, { ...updateData, ...invariantChanges })
+      await entityManager.update(Box, id, { ...safeUpdateData, ...invariantChanges })

Also applies to: 100-101, 157-161, 202-213

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/box/repositories/box.repository.ts` around lines 84 - 87, The
repository update handlers must enforce immutable IDs: in the update and
updateWhere code paths (where Object.assign(box, updateData) is used and where
updateData is applied to items), detect if updateData contains an id and either
strip it out before applying or reject the request with an error; update the
logic that creates previousBox and the subsequent cache invalidation to rely on
the original box.id (do not allow updateData.id to overwrite box.id), and ensure
box.assertValid() runs after applying only permitted fields. Apply the same
check/strip in both update and updateWhere so stale cache entries or
inconsistent relations cannot be introduced.
apps/api/src/box/services/box.service.ts (1)

463-469: ⚠️ Potential issue | 🟠 Major

Make filters.id prefix match case-sensitive for box.id (keep case-insensitive only for box.name).

apps/api/src/box/services/box.service.ts builds idFilter = ILike(${id}%) and uses it for both id and name (lines 463-474). Box IDs are 12-char base62 using both A-Z and a-z, so Postgres ILike (case-insensitive) can return incorrect boxes for an ID-prefix search.

Fix: split the filters—use Like(${id}%) for box.id prefix matching, and keep ILike(${id}%) for prefix matching box.name when filters.id is provided.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/box/services/box.service.ts` around lines 463 - 469, The code
currently sets idFilter = ILike(`${id}%`) and applies it to both the id and name
filters; change this so box id prefix matching is case-sensitive by creating
idFilter = Like(`${id}%`) for the id property, while keeping nameFilter as
ILike(`${id}%`) for the name property; update the return object that spreads
baseFindOptions and nameFilter to set id: idFilter (and ensure any variable
names used in the surrounding function/method that reference idFilter,
nameFilter, baseFindOptions are adjusted accordingly so id uses Like and name
uses ILike).
🧹 Nitpick comments (3)
apps/dashboard/src/hooks/useBoxWsSync.ts (1)

51-54: ⚡ Quick win

Redundant duplicate cache update.

After removing the boxId field, both boxId (the hook parameter) and box.id (from the event payload) resolve to the same identifier when matchesActiveBox passes. Calling updateStateInDetailCache twice with identical keys is wasteful.

♻️ Suggested fix
     const optimisticUpdate = (box: Box, state: BoxState) => {
       updateStateInListCache(box.id, state)
       if (boxId) {
-        updateStateInDetailCache(boxId, state)
-        updateStateInDetailCache(box.id, state)
+        updateStateInDetailCache(box.id, state)
       }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/hooks/useBoxWsSync.ts` around lines 51 - 54, In
useBoxWsSync, remove the redundant cache update: when matchesActiveBox is true
boxId and box.id are identical, so delete the extra call to
updateStateInDetailCache(boxId, state) and keep a single
updateStateInDetailCache(box.id, state) (or vice-versa, but standardize to one
identifier) to avoid duplicate writes.
scripts/test/e2e/bootstrap.sh (1)

146-150: ⚡ Quick win

Pin local allowlist images by digest to avoid supply-chain drift.

Using mutable tags (alpine:3.23, python:3.11-alpine, node:18-alpine) makes e2e behavior non-deterministic as upstream tags move. Prefer digest-pinned refs here too, even for public images, so local runs stay reproducible and safer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test/e2e/bootstrap.sh` around lines 146 - 150, The three mutable
image variables (BOXLITE_SYSTEM_BASE_IMAGE, BOXLITE_SYSTEM_PYTHON_IMAGE,
BOXLITE_SYSTEM_NODE_IMAGE) should be pinned to immutable digests to prevent
supply-chain drift; replace the tag references (alpine:3.23, python:3.11-alpine,
node:18-alpine) with their corresponding sha256@digest image references (e.g.,
alpine:3.23@sha256:<digest>) by resolving the current trusted digests from the
official registries and updating each variable to that digest-pinned ref so
local e2e runs remain reproducible.
apps/api/src/box/runner-adapter/runnerAdapter.v2.createBox.spec.ts (1)

67-70: ⚡ Quick win

Add a gpuQuota assertion in the create payload test.

The suite validates CPU/memory/storage mappings but leaves GPU unmapped/untested. Add expect(payload.gpuQuota).toBe(0) (or the configured value) in this block so create-box sizing coverage is complete.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/box/runner-adapter/runnerAdapter.v2.createBox.spec.ts` around
lines 67 - 70, The create-box test is missing an assertion for GPU sizing; in
the test block that asserts payload.cpuQuota, payload.memoryQuota, and
payload.storageQuota, add an assertion for the GPU field such as
expect(payload.gpuQuota).toBe(0) (or the configured GPU value) so the create
payload mapping covers GPU quota as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api-client-go/api/openapi.yaml`:
- Around line 13469-13472: The OpenAPI field organizationId has a mismatched
description ("Public box ID shown to users"); update the description for the
organizationId property in the OpenAPI schema to accurately describe the
organization identifier (e.g., "Public organization ID shown to users" or
"Organization ID exposed to clients") so it matches the field name and example
and avoids confusion in generated admin API docs.

In `@apps/api-client-go/model_admin_box_item.go`:
- Around line 26-27: Update the OpenAPI spec entry
components/schemas/AdminBoxItem/properties/organizationId.description to a
correct description for the organization ID (not “Public box ID shown to
users”), so the generated Go comment on the AdminBoxItem struct field
OrganizationId (in model_admin_box_item.go) reflects the proper meaning; after
changing the description text in apps/api-client-go/api/openapi.yaml, regenerate
the Go client to apply the updated comment.

In `@apps/api/src/box/managers/box-actions/box-start.action.ts`:
- Around line 68-72: Persist the CREATING state before dispatching the external
create operation: move the this.updateBoxState(box, BoxState.CREATING, lockCode)
call to run before runnerAdapter.createBox (or make both actions
atomic/transactional if supported). Concretely, in the box-start handler adjust
the sequence so updateBoxState is awaited first, then call
runnerAdapter.createBox(box); ensure error handling rolls back or prevents
duplicate enqueue on failure and keep the return SYNC_AGAIN behavior unchanged.

In `@apps/api/src/box/runner-adapter/runnerAdapter.v0.ts`:
- Around line 147-150: New boxes can be assigned a V0 runner that throws in
RunnerAdapterV0.createBox(), so update the runner-selection path to only choose
runners that support createBox (e.g., apiVersion === '2') to prevent boxes
moving to ERROR: modify the code that calls
runnerService.getRandomAvailableRunner() for create/start-creation flows (and
any BoxStartAction logic that invokes runnerAdapter.createBox() when BoxState is
UNKNOWN -> desired STARTED) to filter available runners by apiVersion === '2'
(or alternately add a capability check on the selected runner and retry/select
another runner if createBox is unsupported); ensure the check is applied where
runnerAdapter.createBox() is invoked so only adapters that implement createBox
are ever chosen.

In `@apps/api/src/box/runner-adapter/runnerAdapter.v2.ts`:
- Around line 121-134: The CREATE_BOX payload in runnerAdapter.v2.ts is missing
the GPU quota and thus drops box.gpu; update the payload object used to call
createBox to include the GPU field (e.g., add gpuQuota: box.gpu or the runner's
expected key such as gpu: box.gpu) alongside cpuQuota/memoryQuota/storageQuota,
and ensure any related type/interface for the payload (if present) is updated to
include this GPU property so the runner receives and honors the GPU request.

In `@apps/api/src/migrations/1741087887225-migration.ts`:
- Around line 73-74: The CREATE TABLE for "box" currently uses character
varying(12) for the id column but allows shorter/non-base62 values; add a DB
CHECK constraint to enforce exactly 12 base62 chars by adding e.g. CONSTRAINT
box_id_format_chk CHECK (id ~ '^[A-Za-z0-9]{12}$') into the CREATE TABLE
statement (or ALTER TABLE ... ADD CONSTRAINT for an existing table), and also
ensure the migration's down path removes that constraint (DROP CONSTRAINT
box_id_format_chk) so rollbacks remain consistent.

In `@apps/dashboard/src/components/admin/adminHelpers.ts`:
- Around line 223-227: The lookup uses exact-case matching (const targetBoxId =
boxId.trim(); group.boxes.find(b => b.id === targetBoxId)) but callers may
lowercase pasted ids; change the lookup to first attempt an exact match by id
(keep targetBoxId and the current strict compare), and if that returns
undefined, perform a fallback case-insensitive search (compare
b.id.toLowerCase() === targetBoxId.toLowerCase()) so legitimate IDs with
uppercase characters are still found; update the code around targetBoxId and the
group.boxes.find call accordingly.

In `@apps/libs/api-client/src/docs/AdminBoxItem.md`:
- Line 9: The AdminBoxItemDto.organizationId field has conflicting Swagger
descriptions in admin-overview.dto.ts causing incorrect docs; open the DTO in
admin-overview.dto.ts, locate the decorators applied to
AdminBoxItemDto.organizationId and remove or update the duplicate Swagger
decorator so there is a single `@ApiProperty` (or `@ApiPropertyOptional`) whose
description reads "Organization ID" (not "Public box ID shown to users"), then
regenerate the OpenAPI artifacts and clients/docs (so
apps/api-client-go/api/openapi.yaml and
apps/libs/api-client/src/docs/AdminBoxItem.md are updated).

In `@apps/runner/pkg/boxlite/stubs.go`:
- Around line 95-98: The code is hardcoding "alpine:latest" when building
dto.CreateBoxDTO (createDto) during recovery; change it to use the original box
image instead (e.g., set Image to recoverDto.Image or the
originalBox.Image/box.Image field that contains the box's image tag) so the
recovered box boots the same environment; update any callers/tests that assumed
the hardcoded value if needed.

---

Outside diff comments:
In `@apps/api-client-go/api/openapi.yaml`:
- Around line 10216-10321: The CreateBox schema in openapi.yaml is missing the
image property used by the backend; add an "image" property to the CreateBox
object (matching the required/optional status from the server-side
CreateBoxDto), with type string and an example containing an OCI ref, and then
regenerate clients so they can submit the OCI image ref that the backend
expects; update the CreateBox schema block and ensure the volumes/items and
other existing fields remain unchanged.
- Around line 9938-9979: Update the OpenAPI examples and descriptions to use the
new public id semantics instead of UUID/internal ids: in
apps/api-client-go/api/openapi.yaml (lines 9938-9979) change the Box.id
description to state it's the public exposed ID (not an internal UUID) and
replace the example value (fd955d93-...) with a representative public id (e.g.,
"box_abc123" or the project's public id format) and update any adjacent example
fields that reference the internal UUID; in apps/api-client-go/api/openapi.yaml
(lines 10121-10196) update the paginated boxes example items so each item uses
the public id shape/value (replace UUID examples with the public id format) and
adjust any descriptive text that mentions "internal UUID"; and in
apps/api-client-go/api/openapi.yaml (lines 12487-12529) change the Workspace.id
description/example to the new public id semantics and swap the UUID example for
a representative public workspace id (e.g., "workspace_xyz789"). Ensure the
schema names Box.id and Workspace.id are the ones updated so examples and
generated docs reflect the public id model consistently.

In `@apps/api/src/box/dto/box.dto.ts`:
- Around line 38-41: Update the `@ApiProperty` metadata for the id field in the
Box DTO to reflect the new public 12-character base62 identifier (not a UUID):
change the description to mention "public 12-character base62 ID" and replace
the UUID example with a representative 12-char base62 example (e.g.
"A1b2C3d4E5f6") so Swagger docs and generated examples match the new contract;
locate the `@ApiProperty` decorator applied to the id property in the DTO
(box.dto.ts) and edit its description and example accordingly.

In `@apps/api/src/box/repositories/box.repository.ts`:
- Around line 84-87: The repository update handlers must enforce immutable IDs:
in the update and updateWhere code paths (where Object.assign(box, updateData)
is used and where updateData is applied to items), detect if updateData contains
an id and either strip it out before applying or reject the request with an
error; update the logic that creates previousBox and the subsequent cache
invalidation to rely on the original box.id (do not allow updateData.id to
overwrite box.id), and ensure box.assertValid() runs after applying only
permitted fields. Apply the same check/strip in both update and updateWhere so
stale cache entries or inconsistent relations cannot be introduced.

In `@apps/api/src/box/services/box.service.ts`:
- Around line 463-469: The code currently sets idFilter = ILike(`${id}%`) and
applies it to both the id and name filters; change this so box id prefix
matching is case-sensitive by creating idFilter = Like(`${id}%`) for the id
property, while keeping nameFilter as ILike(`${id}%`) for the name property;
update the return object that spreads baseFindOptions and nameFilter to set id:
idFilter (and ensure any variable names used in the surrounding function/method
that reference idFilter, nameFilter, baseFindOptions are adjusted accordingly so
id uses Like and name uses ILike).

---

Nitpick comments:
In `@apps/api/src/box/runner-adapter/runnerAdapter.v2.createBox.spec.ts`:
- Around line 67-70: The create-box test is missing an assertion for GPU sizing;
in the test block that asserts payload.cpuQuota, payload.memoryQuota, and
payload.storageQuota, add an assertion for the GPU field such as
expect(payload.gpuQuota).toBe(0) (or the configured GPU value) so the create
payload mapping covers GPU quota as well.

In `@apps/dashboard/src/hooks/useBoxWsSync.ts`:
- Around line 51-54: In useBoxWsSync, remove the redundant cache update: when
matchesActiveBox is true boxId and box.id are identical, so delete the extra
call to updateStateInDetailCache(boxId, state) and keep a single
updateStateInDetailCache(box.id, state) (or vice-versa, but standardize to one
identifier) to avoid duplicate writes.

In `@scripts/test/e2e/bootstrap.sh`:
- Around line 146-150: The three mutable image variables
(BOXLITE_SYSTEM_BASE_IMAGE, BOXLITE_SYSTEM_PYTHON_IMAGE,
BOXLITE_SYSTEM_NODE_IMAGE) should be pinned to immutable digests to prevent
supply-chain drift; replace the tag references (alpine:3.23, python:3.11-alpine,
node:18-alpine) with their corresponding sha256@digest image references (e.g.,
alpine:3.23@sha256:<digest>) by resolving the current trusted digests from the
official registries and updating each variable to that digest-pinned ref so
local e2e runs remain reproducible.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 58cdc5d5-4ebe-48ce-adc6-562bbfbb424d

📥 Commits

Reviewing files that changed from the base of the PR and between 6f96da3 and be098b4.

📒 Files selected for processing (61)
  • apps/api-client-go/api/openapi.yaml
  • apps/api-client-go/model_admin_box_item.go
  • apps/api-client-go/model_box.go
  • apps/api-client-go/model_workspace.go
  • apps/api/src/admin/dto/admin-overview.dto.ts
  • apps/api/src/admin/services/observability.service.spec.ts
  • apps/api/src/admin/services/observability.service.ts
  • apps/api/src/admin/services/overview.service.ts
  • apps/api/src/box/constants/curated-images.constant.spec.ts
  • apps/api/src/box/constants/curated-images.constant.ts
  • apps/api/src/box/dto/box.dto.spec.ts
  • apps/api/src/box/dto/box.dto.ts
  • apps/api/src/box/dto/create-box.dto.ts
  • apps/api/src/box/dto/workspace.deprecated.dto.ts
  • apps/api/src/box/entities/box.entity.spec.ts
  • apps/api/src/box/entities/box.entity.ts
  • apps/api/src/box/managers/box-actions/box-start.action.ts
  • apps/api/src/box/repositories/box.repository.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v0.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v2.createBox.spec.ts
  • apps/api/src/box/runner-adapter/runnerAdapter.v2.ts
  • apps/api/src/box/services/box-lookup-cache-invalidation.service.ts
  • apps/api/src/box/services/box.service.box-id.spec.ts
  • apps/api/src/box/services/box.service.ts
  • apps/api/src/box/services/box.service.warm-pool.spec.ts
  • apps/api/src/box/utils/box-id.util.ts
  • apps/api/src/box/utils/box-lookup-cache.util.ts
  • apps/api/src/boxlite-rest/mappers/box-to-box.mapper.spec.ts
  • apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts
  • apps/api/src/migrations/1741087887225-migration.ts
  • apps/dashboard/src/components/BoxTable/BoxTableActions.tsx
  • apps/dashboard/src/components/admin/AdminPeopleBoxesView.tsx
  • apps/dashboard/src/components/admin/adminDiagnoseTarget.ts
  • apps/dashboard/src/components/admin/adminHelpers.ts
  • apps/dashboard/src/hooks/useBoxWsSync.ts
  • apps/dashboard/src/lib/box-identity.ts
  • apps/libs/api-client/src/docs/AdminBoxItem.md
  • apps/libs/api-client/src/docs/Box.md
  • apps/libs/api-client/src/docs/Workspace.md
  • apps/libs/api-client/src/models/admin-box-item.ts
  • apps/libs/api-client/src/models/box.ts
  • apps/libs/api-client/src/models/workspace.ts
  • apps/libs/sdk-typescript/src/Box.ts
  • apps/runner/pkg/api/dto/box.go
  • apps/runner/pkg/boxlite/client.go
  • apps/runner/pkg/boxlite/stubs.go
  • scripts/test/e2e/README.md
  • scripts/test/e2e/bootstrap.sh
  • scripts/test/e2e/cases/conftest.py
  • scripts/test/e2e/cases/test_c_entry.py
  • scripts/test/e2e/cases/test_cli_detach_recovery.py
  • scripts/test/e2e/cases/test_cli_entry.py
  • scripts/test/e2e/cases/test_error_code_mapping.py
  • scripts/test/e2e/cases/test_go_entry.py
  • scripts/test/e2e/cases/test_node_entry.py
  • scripts/test/e2e/cases/test_quota_enforcement.py
  • scripts/test/e2e/fixture_setup.py
  • scripts/test/e2e/sdks/c/e2e_basic.c
  • scripts/test/e2e/sdks/go/e2e_basic.go
  • scripts/test/e2e/sdks/node/e2e_basic.ts
💤 Files with no reviewable changes (6)
  • apps/api/src/admin/services/overview.service.ts
  • apps/api/src/box/utils/box-id.util.ts
  • apps/api/src/admin/dto/admin-overview.dto.ts
  • apps/dashboard/src/components/BoxTable/BoxTableActions.tsx
  • apps/api/src/box/utils/box-lookup-cache.util.ts
  • apps/api/src/box/dto/workspace.deprecated.dto.ts

Comment on lines +13469 to 13472
organizationId:
description: Public box ID shown to users
example: abc123XYZ
type: string

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the organizationId description.

The field name and example are organizationId, but the description still says “Public box ID shown to users”. That will generate contradictory admin API docs.

Suggested doc fix
         organizationId:
-          description: Public box ID shown to users
+          description: Organization ID that owns the box
           example: abc123XYZ
           type: string
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
organizationId:
description: Public box ID shown to users
example: abc123XYZ
type: string
organizationId:
description: Organization ID that owns the box
example: abc123XYZ
type: string
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api-client-go/api/openapi.yaml` around lines 13469 - 13472, The OpenAPI
field organizationId has a mismatched description ("Public box ID shown to
users"); update the description for the organizationId property in the OpenAPI
schema to accurately describe the organization identifier (e.g., "Public
organization ID shown to users" or "Organization ID exposed to clients") so it
matches the field name and example and avoids confusion in generated admin API
docs.

Comment on lines 26 to +27
// Public box ID shown to users
BoxId *string `json:"boxId,omitempty"`
// Organization ID
OrganizationId string `json:"organizationId"`
OrganizationId *string `json:"organizationId,omitempty"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the OpenAPI spec for the AdminBoxItem organizationId description
rg -n -A2 'organizationId' apps/api-client-go/api/openapi.yaml | head -40

Repository: boxlite-ai/boxlite

Length of output: 1019


🏁 Script executed:

#!/bin/bash
# Locate AdminBoxItem schema and inspect organizationId description within it.
set -euo pipefail

file="apps/api-client-go/api/openapi.yaml"

echo "== Matches for AdminBoxItem =="
rg -n "AdminBoxItem" "$file" | head -50

echo
echo "== Search for organizationId within the AdminBoxItem area =="
# Try to find the schema block first, then narrow search
# (Assumes description is near 'AdminBoxItem:' or 'AdminBoxItem' definition.)
rg -n "AdminBoxItem" "$file" | head -1 | awk -F: '{print $1}' > /tmp/adminboxitem_line.txt
admin_line=$(cat /tmp/adminboxitem_line.txt)

# Print a window around the first AdminBoxItem occurrence for manual inspection
start=$((admin_line-80))
end=$((admin_line+200))
sed -n "${start},${end}p" "$file" | nl -ba | head -400

echo
echo "== Direct search for description text =="
rg -n "Public box ID shown to users|boxId.*shown to users|shown to users" "$file" || true

Repository: boxlite-ai/boxlite

Length of output: 366


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="apps/api-client-go/api/openapi.yaml"

echo "== AdminBoxItem schema block (around line 13449) =="
start=13380
end=13650
sed -n "${start},${end}p" "$file" | cat -n

echo
echo "== Find organizationId inside AdminBoxItem =="
rg -n "AdminBoxItem:|organizationId:" "$file" | head -50

echo
echo "== Search for the exact stale phrase =="
rg -n "Public box ID shown to users|boxId.*shown to users|shown to users" "$file" || true

Repository: boxlite-ai/boxlite

Length of output: 11210


Fix misleading AdminBoxItem.organizationId description in the OpenAPI spec.
apps/api-client-go/api/openapi.yaml documents components/schemas/AdminBoxItem/properties/organizationId.description as “Public box ID shown to users”, which is the wrong field meaning (it’s organizationId). Update the description so the generated Go comment on OrganizationId (apps/api-client-go/model_admin_box_item.go lines 26-27) matches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api-client-go/model_admin_box_item.go` around lines 26 - 27, Update the
OpenAPI spec entry
components/schemas/AdminBoxItem/properties/organizationId.description to a
correct description for the organization ID (not “Public box ID shown to
users”), so the generated Go comment on the AdminBoxItem struct field
OrganizationId (in model_admin_box_item.go) reflects the proper meaning; after
changing the description text in apps/api-client-go/api/openapi.yaml, regenerate
the Go client to apply the updated comment.

Comment on lines +68 to +72
const runnerAdapter = await this.runnerAdapterFactory.create(runner)
await runnerAdapter.createBox(box)

await this.updateBoxState(box, BoxState.CREATING, lockCode)
return SYNC_AGAIN

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Persist the state transition before (or atomically with) CREATE_BOX dispatch.

Line 68 dispatches createBox before Line 71 persists CREATING. If persistence fails after enqueue succeeds, retries from UNKNOWN can dispatch duplicate create operations for the same box.

Suggested direction
-    const runnerAdapter = await this.runnerAdapterFactory.create(runner)
-    await runnerAdapter.createBox(box)
-
-    await this.updateBoxState(box, BoxState.CREATING, lockCode)
+    await this.updateBoxState(box, BoxState.CREATING, lockCode)
+    const runnerAdapter = await this.runnerAdapterFactory.create(runner)
+    await runnerAdapter.createBox(box) // treat "already exists" as success in adapter path
     return SYNC_AGAIN
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const runnerAdapter = await this.runnerAdapterFactory.create(runner)
await runnerAdapter.createBox(box)
await this.updateBoxState(box, BoxState.CREATING, lockCode)
return SYNC_AGAIN
await this.updateBoxState(box, BoxState.CREATING, lockCode)
const runnerAdapter = await this.runnerAdapterFactory.create(runner)
await runnerAdapter.createBox(box) // treat "already exists" as success in adapter path
return SYNC_AGAIN
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/box/managers/box-actions/box-start.action.ts` around lines 68 -
72, Persist the CREATING state before dispatching the external create operation:
move the this.updateBoxState(box, BoxState.CREATING, lockCode) call to run
before runnerAdapter.createBox (or make both actions atomic/transactional if
supported). Concretely, in the box-start handler adjust the sequence so
updateBoxState is awaited first, then call runnerAdapter.createBox(box); ensure
error handling rolls back or prevents duplicate enqueue on failure and keep the
return SYNC_AGAIN behavior unchanged.

Comment on lines +147 to +150
async createBox(_box: Box): Promise<void> {
// V0 (direct HTTP) create is out of MVP scope; only V2 (job-based) create is wired.
throw new Error('createBox is not supported for V0 runners')
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Create-box call sites =="
rg -nP '\bcreateBox\s*\(' apps/api/src -g '*.ts' -C 2

echo
echo "== Runner version selection / gating =="
rg -nP '\bapiVersion\b|\bRunnerAdapterFactory\b|\.create\(\s*runner' apps/api/src/box -g '*.ts' -C 3

echo
echo "== Any explicit prohibition/upgrade checks for v0 on create =="
rg -nP 'v0|version.?0|Unsupported runner version|not supported for V0|create.*supported' apps/api/src/box -g '*.ts' -C 2

Repository: boxlite-ai/boxlite

Length of output: 37768


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate where CREATE transitions happen (create/start orchestration) =="
rg -n "BoxState\.CREATING|CREATE_BOX|createBoxDto|createBox\(" apps/api/src/box -g '*.ts'

echo
echo "== In box-start.action.ts: check runner selection / any apiVersion gating for createBox =="
sed -n '1,240p' apps/api/src/box/managers/box-actions/box-start.action.ts | nl -ba

echo
echo "== In box.service.ts: check createBox / runner selection logic near creates =="
rg -n "createBox\(|startBox\(|BoxState\.CREATING" apps/api/src/box/services/box.service.ts -g '*.ts'
sed -n '760,980p' apps/api/src/box/services/box.service.ts | nl -ba

Repository: boxlite-ai/boxlite

Length of output: 8820


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== BoxStartAction: find BoxState.CREATING branch and createBox call context =="
rg -n "class BoxStartAction|case BoxState\.CREATING|createBox\(" apps/api/src/box/managers/box-actions/box-start.action.ts
echo
echo "== Print around createBox() call (first ~130 lines where it's referenced) =="
sed -n '1,200p' apps/api/src/box/managers/box-actions/box-start.action.ts | cat -n
echo
echo "== Print later portion around BoxState.CREATING handler (where switch likely continues) =="
sed -n '200,380p' apps/api/src/box/managers/box-actions/box-start.action.ts | cat -n

echo
echo "== BoxService: look for runner selection / apiVersion filtering during create =="
rg -n "createBoxInternal|createFromTemplate|BoxState\.CREATING|runner.*apiVersion|apiVersion.*runner|warmPool|find.*runner|select.*runner" apps/api/src/box/services/box.service.ts -g '*.ts'
echo
echo "== Print createBoxInternal (around line ~130-260 from prior rg) =="
sed -n '120,320p' apps/api/src/box/services/box.service.ts | cat -n
echo
echo "== Print any runner-selection logic nearby (around where runnerId is assigned during create) =="
rg -n "runnerId\s*=|\.runnerId\s*=" apps/api/src/box/services/box.service.ts -g '*.ts'
rg -n "warmPool|WarmPool" apps/api/src/box/services/box.service.ts -g '*.ts'
echo
sed -n '320,520p' apps/api/src/box/services/box.service.ts | cat -n

Repository: boxlite-ai/boxlite

Length of output: 28784


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== runner.service.ts: locate getRandomAvailableRunner =="
rg -n "getRandomAvailableRunner" apps/api/src/box/services/runner.service.ts

echo
echo "== Show implementation of getRandomAvailableRunner (likely around its definition) =="
# Print a generous window around the function definition
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("apps/api/src/box/services/runner.service.ts")
txt = p.read_text()
m = re.search(r'dasync\s+getRandomAvailableRunner|async\s+getRandomAvailableRunner', txt)
print("First occurrence index:", m.start() if m else None)
PY
# Fallback: just dump a mid-sized slice where it likely lives
sed -n '1,220p' apps/api/src/box/services/runner.service.ts | cat -n
sed -n '220,520p' apps/api/src/box/services/runner.service.ts | cat -n
sed -n '520,820p' apps/api/src/box/services/runner.service.ts | cat -n

echo
echo "== warm pool selection: search for apiVersion constraints in runner selection helpers =="
rg -n "getRandomAvailableRunner|getAvailableRunner|warmPool|WarmPool|apiVersion\s*===\s*'0'|apiVersion:\s*'0'|case '0'" apps/api/src/box/services -g '*.ts'

echo
echo "== Warm-pool related runner selection in box-warm-pool service (if present) =="
rg -n "getRandomAvailableRunner|runnerId|apiVersion" apps/api/src/box/services/box-warm-pool.service.ts -g '*.ts' || true
ls -la apps/api/src/box/services | sed -n '1,200p'

Repository: boxlite-ai/boxlite

Length of output: 45510


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== box.service.ts: createForWarmPool (runner selection) =="
sed -n '100,220p' apps/api/src/box/services/box.service.ts | cat -n

echo
echo "== box.service.ts: createBoxInternal (runner selection) =="
sed -n '140,240p' apps/api/src/box/services/box.service.ts | cat -n

echo
echo "== runner.service.ts: findAvailableRunners (filters) =="
sed -n '60,140p' apps/api/src/box/services/runner.service.ts | cat -n

echo
echo "== warm-pool: any apiVersion filtering in box-warm-pool.service.ts =="
rg -n "apiVersion" apps/api/src/box/services/box-warm-pool.service.ts -g '*.ts' || true
sed -n '1,260p' apps/api/src/box/services/box-warm-pool.service.ts | cat -n

Repository: boxlite-ai/boxlite

Length of output: 23599


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Box entity: default state / desiredState on construction =="
rg -n "class Box|constructor\(|desiredState|state:" apps/api/src/box/entities/box.entity.ts
sed -n '1,220p' apps/api/src/box/entities/box.entity.ts | cat -n
sed -n '220,420p' apps/api/src/box/entities/box.entity.ts | cat -n

echo
echo "== Find where BoxStartAction is triggered for desired STARTED =="
rg -n "BoxStartAction|desiredState.*STARTED|BoxDesiredState\.STARTED|BoxStateWaiterService|handleRunnerBoxUnknownStateOnDesiredStateStart" apps/api/src/box -g '*.ts'

Repository: boxlite-ai/boxlite

Length of output: 14231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' apps/api/src/box/runner-adapter/runnerAdapter.ts | cat -n

Repository: boxlite-ai/boxlite

Length of output: 3281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== box.manager.ts: where BoxStartAction is called for pending boxes =="
rg -n "boxStartAction\.run|BoxStartAction|desiredState|pending" apps/api/src/box/managers/box.manager.ts
sed -n '1,220p' apps/api/src/box/managers/box.manager.ts | cat -n
sed -n '220,420p' apps/api/src/box/managers/box.manager.ts | cat -n

Repository: boxlite-ai/boxlite

Length of output: 19863


Prevent V0 runners from being selected for box CREATE (current path will throw and put boxes in ERROR).

RunnerAdapterV0.createBox() unconditionally throws (createBox is not supported for V0 runners). New boxes are assigned a runner via runnerService.getRandomAvailableRunner() (no apiVersion filtering—only RunnerState.READY/unschedulable/draining/score), and BoxStartAction calls runnerAdapter.createBox() when the box is in BoxState.UNKNOWN with desired STARTED. If any V0 runner is READY, box creation can fail and transition the box to ERROR.

Fix by gating runner selection to apiVersion='2' for create/start-creation paths (or ensure the selected adapter supports createBox).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/box/runner-adapter/runnerAdapter.v0.ts` around lines 147 - 150,
New boxes can be assigned a V0 runner that throws in
RunnerAdapterV0.createBox(), so update the runner-selection path to only choose
runners that support createBox (e.g., apiVersion === '2') to prevent boxes
moving to ERROR: modify the code that calls
runnerService.getRandomAvailableRunner() for create/start-creation flows (and
any BoxStartAction logic that invokes runnerAdapter.createBox() when BoxState is
UNKNOWN -> desired STARTED) to filter available runners by apiVersion === '2'
(or alternately add a capability check on the selected runner and retry/select
another runner if createBox is unsupported); ensure the check is applied where
runnerAdapter.createBox() is invoked so only adapters that implement createBox
are ever chosen.

Comment on lines +121 to +134
const payload = {
id: box.id,
userId: box.organizationId,
image: box.image,
osUser: box.osUser,
cpuQuota: box.cpu,
memoryQuota: box.mem,
storageQuota: box.disk,
env: box.env,
networkBlockAll: box.networkBlockAll,
networkAllowList: box.networkAllowList,
organizationId: box.organizationId,
regionId: box.region,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

CREATE_BOX payload drops GPU quota, breaking resource contract.

box.gpu is set upstream, but createBox never sends a GPU field. That means GPU requests can be silently downgraded when the runner provisions from this payload.

Suggested fix
     const payload = {
       id: box.id,
       userId: box.organizationId,
       image: box.image,
       osUser: box.osUser,
       cpuQuota: box.cpu,
+      gpuQuota: box.gpu,
       memoryQuota: box.mem,
       storageQuota: box.disk,
       env: box.env,
       networkBlockAll: box.networkBlockAll,
       networkAllowList: box.networkAllowList,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/box/runner-adapter/runnerAdapter.v2.ts` around lines 121 - 134,
The CREATE_BOX payload in runnerAdapter.v2.ts is missing the GPU quota and thus
drops box.gpu; update the payload object used to call createBox to include the
GPU field (e.g., add gpuQuota: box.gpu or the runner's expected key such as gpu:
box.gpu) alongside cpuQuota/memoryQuota/storageQuota, and ensure any related
type/interface for the payload (if present) is updated to include this GPU
property so the runner receives and honors the GPU request.

Comment on lines +73 to 74
`CREATE TABLE "box" ("id" character varying(12) NOT NULL, "organizationId" uuid NOT NULL, "name" character varying NOT NULL, "region" character varying NOT NULL, "runnerId" uuid, "prevRunnerId" uuid, "class" "public"."box_class_enum" NOT NULL DEFAULT 'small', "state" "public"."box_state_enum" NOT NULL DEFAULT 'unknown', "desiredState" "public"."box_desiredstate_enum" NOT NULL DEFAULT 'started', "osUser" character varying NOT NULL, "image" character varying NOT NULL, "errorReason" character varying, "recoverable" boolean NOT NULL DEFAULT false, "env" jsonb NOT NULL DEFAULT '{}', "public" boolean NOT NULL DEFAULT false, "networkBlockAll" boolean NOT NULL DEFAULT false, "networkAllowList" character varying, "labels" jsonb, "cpu" integer NOT NULL DEFAULT '2', "gpu" integer NOT NULL DEFAULT '0', "mem" integer NOT NULL DEFAULT '4', "disk" integer NOT NULL DEFAULT '10', "volumes" jsonb NOT NULL DEFAULT '[]', "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "autoStopInterval" integer NOT NULL DEFAULT '15', "autoDeleteInterval" integer NOT NULL DEFAULT '-1', "pending" boolean NOT NULL DEFAULT false, "authToken" character varying NOT NULL, "daemonVersion" character varying, CONSTRAINT "box_organizationId_name_unique" UNIQUE ("organizationId", "name"), CONSTRAINT "box_id_pk" PRIMARY KEY ("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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce exact 12-char base62 box.id at the DB boundary.

Line 73 uses character varying(12), which only caps length; it still permits shorter/non-base62 IDs. Given the single 12-char base62 identity contract, add a DB check constraint so malformed IDs can’t persist.

Suggested migration patch
     await queryRunner.query(
       `CREATE TABLE "box" ("id" character varying(12) NOT NULL, "organizationId" uuid NOT NULL, "name" character varying NOT NULL, "region" character varying NOT NULL, "runnerId" uuid, "prevRunnerId" uuid, "class" "public"."box_class_enum" NOT NULL DEFAULT 'small', "state" "public"."box_state_enum" NOT NULL DEFAULT 'unknown', "desiredState" "public"."box_desiredstate_enum" NOT NULL DEFAULT 'started', "osUser" character varying NOT NULL, "image" character varying NOT NULL, "errorReason" character varying, "recoverable" boolean NOT NULL DEFAULT false, "env" jsonb NOT NULL DEFAULT '{}', "public" boolean NOT NULL DEFAULT false, "networkBlockAll" boolean NOT NULL DEFAULT false, "networkAllowList" character varying, "labels" jsonb, "cpu" integer NOT NULL DEFAULT '2', "gpu" integer NOT NULL DEFAULT '0', "mem" integer NOT NULL DEFAULT '4', "disk" integer NOT NULL DEFAULT '10', "volumes" jsonb NOT NULL DEFAULT '[]', "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "autoStopInterval" integer NOT NULL DEFAULT '15', "autoDeleteInterval" integer NOT NULL DEFAULT '-1', "pending" boolean NOT NULL DEFAULT false, "authToken" character varying NOT NULL, "daemonVersion" character varying, CONSTRAINT "box_organizationId_name_unique" UNIQUE ("organizationId", "name"), CONSTRAINT "box_id_pk" PRIMARY KEY ("id"))`,
     )
+    await queryRunner.query(
+      `ALTER TABLE "box" ADD CONSTRAINT "box_id_format_chk" CHECK ("id" ~ '^[0-9A-Za-z]{12}$')`,
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`CREATE TABLE "box" ("id" character varying(12) NOT NULL, "organizationId" uuid NOT NULL, "name" character varying NOT NULL, "region" character varying NOT NULL, "runnerId" uuid, "prevRunnerId" uuid, "class" "public"."box_class_enum" NOT NULL DEFAULT 'small', "state" "public"."box_state_enum" NOT NULL DEFAULT 'unknown', "desiredState" "public"."box_desiredstate_enum" NOT NULL DEFAULT 'started', "osUser" character varying NOT NULL, "image" character varying NOT NULL, "errorReason" character varying, "recoverable" boolean NOT NULL DEFAULT false, "env" jsonb NOT NULL DEFAULT '{}', "public" boolean NOT NULL DEFAULT false, "networkBlockAll" boolean NOT NULL DEFAULT false, "networkAllowList" character varying, "labels" jsonb, "cpu" integer NOT NULL DEFAULT '2', "gpu" integer NOT NULL DEFAULT '0', "mem" integer NOT NULL DEFAULT '4', "disk" integer NOT NULL DEFAULT '10', "volumes" jsonb NOT NULL DEFAULT '[]', "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "autoStopInterval" integer NOT NULL DEFAULT '15', "autoDeleteInterval" integer NOT NULL DEFAULT '-1', "pending" boolean NOT NULL DEFAULT false, "authToken" character varying NOT NULL, "daemonVersion" character varying, CONSTRAINT "box_organizationId_name_unique" UNIQUE ("organizationId", "name"), CONSTRAINT "box_id_pk" PRIMARY KEY ("id"))`,
)
await queryRunner.query(
`CREATE TABLE "box" ("id" character varying(12) NOT NULL, "organizationId" uuid NOT NULL, "name" character varying NOT NULL, "region" character varying NOT NULL, "runnerId" uuid, "prevRunnerId" uuid, "class" "public"."box_class_enum" NOT NULL DEFAULT 'small', "state" "public"."box_state_enum" NOT NULL DEFAULT 'unknown', "desiredState" "public"."box_desiredstate_enum" NOT NULL DEFAULT 'started', "osUser" character varying NOT NULL, "image" character varying NOT NULL, "errorReason" character varying, "recoverable" boolean NOT NULL DEFAULT false, "env" jsonb NOT NULL DEFAULT '{}', "public" boolean NOT NULL DEFAULT false, "networkBlockAll" boolean NOT NULL DEFAULT false, "networkAllowList" character varying, "labels" jsonb, "cpu" integer NOT NULL DEFAULT '2', "gpu" integer NOT NULL DEFAULT '0', "mem" integer NOT NULL DEFAULT '4', "disk" integer NOT NULL DEFAULT '10', "volumes" jsonb NOT NULL DEFAULT '[]', "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "autoStopInterval" integer NOT NULL DEFAULT '15', "autoDeleteInterval" integer NOT NULL DEFAULT '-1', "pending" boolean NOT NULL DEFAULT false, "authToken" character varying NOT NULL, "daemonVersion" character varying, CONSTRAINT "box_organizationId_name_unique" UNIQUE ("organizationId", "name"), CONSTRAINT "box_id_pk" PRIMARY KEY ("id"))`,
)
await queryRunner.query(
`ALTER TABLE "box" ADD CONSTRAINT "box_id_format_chk" CHECK ("id" ~ '^[0-9A-Za-z]{12}$')`,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/migrations/1741087887225-migration.ts` around lines 73 - 74, The
CREATE TABLE for "box" currently uses character varying(12) for the id column
but allows shorter/non-base62 values; add a DB CHECK constraint to enforce
exactly 12 base62 chars by adding e.g. CONSTRAINT box_id_format_chk CHECK (id ~
'^[A-Za-z0-9]{12}$') into the CREATE TABLE statement (or ALTER TABLE ... ADD
CONSTRAINT for an existing table), and also ensure the migration's down path
removes that constraint (DROP CONSTRAINT box_id_format_chk) so rollbacks remain
consistent.

Comment on lines +223 to +227
// Box ids are 12-char base62 and case-sensitive; a case-insensitive match could resolve
// to the wrong box when two valid ids differ only by case.
const targetBoxId = boxId.trim()
for (const group of groups) {
const box = group.boxes.find((b) => b.id.toLowerCase() === targetBoxId || b.boxId?.toLowerCase() === targetBoxId)
const box = group.boxes.find((b) => b.id === targetBoxId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Case-sensitive lookup now conflicts with existing lowercased caller input.

Line 225/Line 227 enforce exact-case id matching, but the current caller path (apps/dashboard/src/pages/Admin.tsx:86-94 in provided context) lowercases the pasted value before findBoxById. That causes legitimate IDs with uppercase characters to fail lookup.

Suggested fix (caller-side, outside this file)
- const trimmed = value.trim().toLowerCase()
+ const trimmed = value.trim()
  if (!trimmed) return

  const boxHit = findBoxById(groupBoxesByOwner(boxesQuery.data ?? []), trimmed)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/dashboard/src/components/admin/adminHelpers.ts` around lines 223 - 227,
The lookup uses exact-case matching (const targetBoxId = boxId.trim();
group.boxes.find(b => b.id === targetBoxId)) but callers may lowercase pasted
ids; change the lookup to first attempt an exact match by id (keep targetBoxId
and the current strict compare), and if that returns undefined, perform a
fallback case-insensitive search (compare b.id.toLowerCase() ===
targetBoxId.toLowerCase()) so legitimate IDs with uppercase characters are still
found; update the code around targetBoxId and the group.boxes.find call
accordingly.

**id** | **string** | Box ID | [default to undefined]
**boxId** | **string** | Public box ID shown to users | [optional] [default to undefined]
**organizationId** | **string** | Organization ID | [default to undefined]
**organizationId** | **string** | Public box ID shown to users | [optional] [default to undefined]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the organizationId description in the OpenAPI spec and other generated docs

# Check the OpenAPI spec for AdminBoxItem organizationId description
echo "=== Checking OpenAPI spec for AdminBoxItem.organizationId ==="
rg -A5 -B2 'organizationId' apps/api-client-go/api/openapi.yaml | head -50

# Check if other model docs have correct organizationId descriptions
echo ""
echo "=== Checking other model docs for organizationId descriptions ==="
rg -n 'organizationId.*\|' apps/libs/api-client/src/docs/

Repository: boxlite-ai/boxlite

Length of output: 11513


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate AdminBoxItem schema in OpenAPI spec and inspect organizationId description
echo "=== Searching for AdminBoxItem in OpenAPI ==="
rg -n "AdminBoxItem" apps/api-client-go/api/openapi.yaml

echo ""
echo "=== Extracting AdminBoxItem schema block (best-effort) ==="
# Try to capture a reasonably-sized neighborhood around the schema
rg -n -C 20 "AdminBoxItem" apps/api-client-go/api/openapi.yaml | head -200

echo ""
echo "=== Looking specifically for organizationId within AdminBoxItem ==="
# Constrain search to nearby lines around AdminBoxItem occurrences
# (If schema is in components/schemas, this should still hit the right spot.)
rg -n -C 10 "AdminBoxItem" apps/api-client-go/api/openapi.yaml | rg -n "organizationId|public box|box id|boxId" || true

Repository: boxlite-ai/boxlite

Length of output: 4404


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print the exact description for AdminBoxItem.organizationId from the OpenAPI schema
echo "=== OpenAPI: AdminBoxItem.organizationId description ==="
rg -n "AdminBoxItem:" apps/api-client-go/api/openapi.yaml
# Dump a tight window around the organizationId property within AdminBoxItem
# (The previous hit indicates organizationId starts around line ~13469)
sed -n '13460,13490p' apps/api-client-go/api/openapi.yaml

echo ""
echo "=== Docs: apps/libs/api-client/src/docs/AdminBoxItem.md (around line 9) ==="
nl -ba apps/libs/api-client/src/docs/AdminBoxItem.md | sed -n '1,30p'

Repository: boxlite-ai/boxlite

Length of output: 1155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Find definitions of AdminBoxItem / organizationId in backend code ==="
rg -n --hidden --no-ignore-vcs "AdminBoxItem" . | head -200

echo ""
echo "=== Find model/DTO fields named organizationId that relate to boxes ==="
rg -n --hidden --no-ignore-vcs "organizationId" apps src . | head -200

echo ""
echo "=== Search for admin overview boxes response mapping ==="
rg -n --hidden --no-ignore-vcs "adminListBoxes|/admin/overview/boxes|admin overview boxes" . | head -200

echo ""
echo "=== Look for any mention of boxId vs id/public box id ==="
rg -n --hidden --no-ignore-vcs "boxId|public box id|public box|Public box" . | head -200

Repository: boxlite-ai/boxlite

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Inspect AdminBoxItemDto definition ==="
rg -n "export class AdminBoxItemDto|organizationId" apps/api/src/admin/dto/admin-overview.dto.ts
echo ""
echo "=== Show the AdminBoxItemDto class block around organizationId ==="
# Print a tight region around the class and the organizationId decorator/property
rg -n "export class AdminBoxItemDto" -n apps/api/src/admin/dto/admin-overview.dto.ts | head -5 | cut -d: -f1 > /tmp/class_line.txt
class_line=$(cat /tmp/class_line.txt)
start=$((class_line-30))
end=$((class_line+120))
sed -n "${start},${end}p" apps/api/src/admin/dto/admin-overview.dto.ts

echo ""
echo "=== Inspect overview.service.ts listBoxes mapping ==="
rg -n "listBoxes\\(|AdminBoxItemDto|organizationId|id\\b" apps/api/src/admin/services/overview.service.ts | head -80
echo ""
echo "=== Show listBoxes implementation region ==="
sed -n '1,220p' apps/api/src/admin/services/overview.service.ts

echo ""
echo "=== Inspect overview.controller.ts response and DTO usage ==="
sed -n '1,140p' apps/api/src/admin/controllers/overview.controller.ts

Repository: boxlite-ai/boxlite

Length of output: 14225


Fix incorrect organizationId description in AdminBoxItem docs/OpenAPI

AdminBoxItemDto.organizationId is documented as “Public box ID shown to users”, but it represents the organization’s ID (the box public ID is id). The incorrect description is emitted into apps/api-client-go/api/openapi.yaml and apps/libs/api-client/src/docs/AdminBoxItem.md.

Root cause: apps/api/src/admin/dto/admin-overview.dto.ts defines two Swagger decorators for organizationId with conflicting descriptions; align to a single “Organization ID” description and regenerate docs/clients.

📝 Suggested fix
-**organizationId** | **string** | Public box ID shown to users | [optional] [default to undefined]
+**organizationId** | **string** | Organization ID | [optional] [default to undefined]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**organizationId** | **string** | Public box ID shown to users | [optional] [default to undefined]
**organizationId** | **string** | Organization ID | [optional] [default to undefined]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/libs/api-client/src/docs/AdminBoxItem.md` at line 9, The
AdminBoxItemDto.organizationId field has conflicting Swagger descriptions in
admin-overview.dto.ts causing incorrect docs; open the DTO in
admin-overview.dto.ts, locate the decorators applied to
AdminBoxItemDto.organizationId and remove or update the duplicate Swagger
decorator so there is a single `@ApiProperty` (or `@ApiPropertyOptional`) whose
description reads "Organization ID" (not "Public box ID shown to users"), then
regenerate the OpenAPI artifacts and clients/docs (so
apps/api-client-go/api/openapi.yaml and
apps/libs/api-client/src/docs/AdminBoxItem.md are updated).

Comment on lines 95 to 98
createDto := dto.CreateBoxDTO{
Id: boxId,
ArtifactRef: "alpine:latest",
Image: "alpine:latest",
OsUser: recoverDto.OsUser,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recovery should preserve the box’s original image instead of forcing alpine:latest.

Line 97 hardcodes a mutable image/tag for all recoveries, which can boot a different environment than the box being recovered.

Suggested fix
 func (c *Client) RecoverBox(ctx context.Context, boxId string, recoverDto dto.RecoverBoxDTO) error {
 	c.logger.Info("recover box", "box", boxId)
+
+	bx, err := c.getOrFetchBox(ctx, boxId)
+	if err != nil {
+		return fmt.Errorf("failed to get box for recover: %w", err)
+	}
+	info, err := bx.Info(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to get box info for recover: %w", err)
+	}
 
 	if err := c.Destroy(ctx, boxId); err != nil {
 		c.logger.Warn("failed to destroy during recover", "error", err)
 	}
 
 	createDto := dto.CreateBoxDTO{
 		Id:               boxId,
-		Image:            "alpine:latest",
+		Image:            info.Image,
 		OsUser:           recoverDto.OsUser,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
createDto := dto.CreateBoxDTO{
Id: boxId,
ArtifactRef: "alpine:latest",
Image: "alpine:latest",
OsUser: recoverDto.OsUser,
func (c *Client) RecoverBox(ctx context.Context, boxId string, recoverDto dto.RecoverBoxDTO) error {
c.logger.Info("recover box", "box", boxId)
bx, err := c.getOrFetchBox(ctx, boxId)
if err != nil {
return fmt.Errorf("failed to get box for recover: %w", err)
}
info, err := bx.Info(ctx)
if err != nil {
return fmt.Errorf("failed to get box info for recover: %w", err)
}
if err := c.Destroy(ctx, boxId); err != nil {
c.logger.Warn("failed to destroy during recover", "error", err)
}
createDto := dto.CreateBoxDTO{
Id: boxId,
Image: info.Image,
OsUser: recoverDto.OsUser,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/runner/pkg/boxlite/stubs.go` around lines 95 - 98, The code is
hardcoding "alpine:latest" when building dto.CreateBoxDTO (createDto) during
recovery; change it to use the original box image instead (e.g., set Image to
recoverDto.Image or the originalBox.Image/box.Image field that contains the
box's image tag) so the recovered box boots the same environment; update any
callers/tests that assumed the hardcoded value if needed.

@law-chain-hot

Copy link
Copy Markdown
Contributor Author

Split up. Superseded by:

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