From 10ab50092ffe0f7c7a4f58b8c05b962ca228260f Mon Sep 17 00:00:00 2001 From: thesujai Date: Thu, 8 Oct 2026 16:32:18 +0530 Subject: [PATCH 1/3] minor refactor sandbox environment --- .changeset/daytona-sandbox-create-on-error.md | 6 +++++ .../core/sandbox/provider/DaytonaProvider.ts | 25 ++++++++----------- .../trueforge/src/sandbox/providerUtils.ts | 5 +++- 3 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 .changeset/daytona-sandbox-create-on-error.md diff --git a/.changeset/daytona-sandbox-create-on-error.md b/.changeset/daytona-sandbox-create-on-error.md new file mode 100644 index 000000000..673339131 --- /dev/null +++ b/.changeset/daytona-sandbox-create-on-error.md @@ -0,0 +1,6 @@ +--- +'@truefoundry/trueforge-core': patch +'@truefoundry/trueforge': patch +--- + +Rename Daytona sandbox create error hook to `onError` so the host decides what to alert on, and rename `createFreshSandbox` to `createNewSandbox`. diff --git a/packages/trueforge-core/src/core/sandbox/provider/DaytonaProvider.ts b/packages/trueforge-core/src/core/sandbox/provider/DaytonaProvider.ts index c8376d311..c11c20e89 100644 --- a/packages/trueforge-core/src/core/sandbox/provider/DaytonaProvider.ts +++ b/packages/trueforge-core/src/core/sandbox/provider/DaytonaProvider.ts @@ -81,8 +81,8 @@ export interface DaytonaSandboxProviderOptions { /** Defaults to 1 hour (same as the gateway's max agent execution time). */ previewUrlExpirySeconds?: number; logger: Logger; - /** Host alert hook (e.g. Sentry P1). */ - onCriticalAlert?: (error: Error) => void; + /** Host hook for create failures; the host decides what to alert on. */ + onError?: (error: Error) => void; } export class DaytonaSandboxProvider implements SandboxProvider { @@ -99,7 +99,7 @@ export class DaytonaSandboxProvider implements SandboxProvider void) | undefined; + private readonly onError: ((error: Error) => void) | undefined; private readonly daytona: Daytona; private static readonly cachedSandboxes = new Map(); // De-dupes concurrent recovery attempts on the same sandbox to a single refreshData+start round-trip. @@ -117,7 +117,7 @@ export class DaytonaSandboxProvider implements SandboxProvider { + /** New create: reject an inactive tip before calling Daytona create. */ + private async createNewSandbox(environment?: DaytonaSandboxEnvironment): Promise { const env = this.requireEnvironment(environment); try { await this.rejectIfSnapshotInactive(env.snapshot_ref); return await this.daytona.create(this.buildCreateParams(env)); } catch (error) { - if (error instanceof DaytonaError && (error.statusCode === 401 || error.statusCode === 403)) { - throw error; - } - const alertError = error instanceof Error ? error : new Error('Daytona sandbox create failed', { cause: error }); + const createError = error instanceof Error ? error : new Error('Daytona sandbox create failed', { cause: error }); this.logger.error('Daytona sandbox create failed', { snapshot_ref: env.snapshot_ref, - ...extractErrorLogFields(alertError), + ...extractErrorLogFields(createError), }); - this.onCriticalAlert?.(alertError); + this.onError?.(createError); throw error; } } @@ -372,9 +369,7 @@ export class DaytonaSandboxProvider implements SandboxProvider { + onError: error => { + if (isDaytonaAuthError(error) || isDaytonaPermissionError(error)) { + return; + } captureCriticalException(error, { tags: { module: 'DaytonaProvider', operation: 'sandboxCreate', priority: 'P1' }, }); From 0f8defa6515bdc72b40c94d9fc3fcd5a03487277 Mon Sep 17 00:00:00 2001 From: thesujai Date: Thu, 8 Oct 2026 16:35:41 +0530 Subject: [PATCH 2/3] Remove Daytona sandbox create error hook documentation and rename related functions for clarity. --- .changeset/daytona-sandbox-create-on-error.md | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .changeset/daytona-sandbox-create-on-error.md diff --git a/.changeset/daytona-sandbox-create-on-error.md b/.changeset/daytona-sandbox-create-on-error.md deleted file mode 100644 index 673339131..000000000 --- a/.changeset/daytona-sandbox-create-on-error.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@truefoundry/trueforge-core': patch -'@truefoundry/trueforge': patch ---- - -Rename Daytona sandbox create error hook to `onError` so the host decides what to alert on, and rename `createFreshSandbox` to `createNewSandbox`. From 2bcd649804e4e9a632ee3657a143da935b683f23 Mon Sep 17 00:00:00 2001 From: thesujai Date: Mon, 19 Oct 2026 21:39:24 +0530 Subject: [PATCH 3/3] Scope Daytona onError to create path and add changeset. Pass onError through getOrCreateSandbox so only fresh creates notify the host, and throw the normalized createError. Co-authored-by: Cursor --- .changeset/daytona-sandbox-create-on-error.md | 6 +++ .../core/sandbox/provider/DaytonaProvider.ts | 48 +++++++++++++------ 2 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 .changeset/daytona-sandbox-create-on-error.md diff --git a/.changeset/daytona-sandbox-create-on-error.md b/.changeset/daytona-sandbox-create-on-error.md new file mode 100644 index 000000000..15826afa5 --- /dev/null +++ b/.changeset/daytona-sandbox-create-on-error.md @@ -0,0 +1,6 @@ +--- +'@truefoundry/trueforge-core': patch +'@truefoundry/trueforge': patch +--- + +Surface Daytona sandbox create failures through a host `onError` hook (authz still ignored for P1) instead of a provider-owned critical alert callback. diff --git a/packages/trueforge-core/src/core/sandbox/provider/DaytonaProvider.ts b/packages/trueforge-core/src/core/sandbox/provider/DaytonaProvider.ts index c11c20e89..7c35f9a24 100644 --- a/packages/trueforge-core/src/core/sandbox/provider/DaytonaProvider.ts +++ b/packages/trueforge-core/src/core/sandbox/provider/DaytonaProvider.ts @@ -81,7 +81,10 @@ export interface DaytonaSandboxProviderOptions { /** Defaults to 1 hour (same as the gateway's max agent execution time). */ previewUrlExpirySeconds?: number; logger: Logger; - /** Host hook for create failures; the host decides what to alert on. */ + /** + * Host hook for fresh-create failures only (wired through getOrCreateSandbox). + * Other provider catch paths do not call this; the host decides what to alert on. + */ onError?: (error: Error) => void; } @@ -341,7 +344,13 @@ export class DaytonaSandboxProvider implements SandboxProvider { + private async createNewSandbox({ + environment, + onError, + }: { + environment?: DaytonaSandboxEnvironment; + onError?: (error: Error) => void; + }): Promise { const env = this.requireEnvironment(environment); try { await this.rejectIfSnapshotInactive(env.snapshot_ref); @@ -352,15 +361,21 @@ export class DaytonaSandboxProvider implements SandboxProvider { + private async getOrCreateSandbox({ + sandboxId, + environment, + onError, + }: { + sandboxId?: string; + environment?: DaytonaSandboxEnvironment; + /** Only used on the fresh-create path; restore/exec paths omit this. */ + onError?: (error: Error) => void; + }): Promise<{ sandbox: Sandbox; defaultTimeoutMs: number }> { if (sandboxId) { validateSandboxOwnedByTenant({ sandboxId, tenantName: this.tenantName }); const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId); @@ -369,7 +384,9 @@ export class DaytonaSandboxProvider implements SandboxProvider { return context.with(suppressTracing(context.active()), async () => { - const { sandbox } = await this.getOrCreateSandbox(undefined, environment); + const { sandbox } = await this.getOrCreateSandbox({ + environment, + onError: this.onError, + }); this.logger.debug(`Sandbox created: name=${sandbox.name}`); return { sandboxId: sandbox.name }; }); @@ -481,7 +501,7 @@ export class DaytonaSandboxProvider implements SandboxProvider => { try { return await this.executeWithSandboxRecovery(params.sandboxId, async () => { - const { sandbox, defaultTimeoutMs } = await this.getOrCreateSandbox(params.sandboxId); + const { sandbox, defaultTimeoutMs } = await this.getOrCreateSandbox({ sandboxId: params.sandboxId }); const response = await sandbox.process.executeCommand( params.command, params.cwd, @@ -514,7 +534,7 @@ export class DaytonaSandboxProvider implements SandboxProvider { try { return await this.executeWithSandboxRecovery(params.sandboxId, async () => { - const { sandbox } = await this.getOrCreateSandbox(params.sandboxId); + const { sandbox } = await this.getOrCreateSandbox({ sandboxId: params.sandboxId }); const info = await this.getFileInfo(sandbox, params.path); if (info.isDir) { @@ -543,7 +563,7 @@ export class DaytonaSandboxProvider implements SandboxProvider { try { await this.executeWithSandboxRecovery(params.sandboxId, async () => { - const { sandbox } = await this.getOrCreateSandbox(params.sandboxId); + const { sandbox } = await this.getOrCreateSandbox({ sandboxId: params.sandboxId }); await sandbox.fs.uploadFile(params.content, params.remotePath); }); } catch (e: unknown) { @@ -558,7 +578,7 @@ export class DaytonaSandboxProvider implements SandboxProvider { try { return await this.executeWithSandboxRecovery(params.sandboxId, async () => { - const { sandbox } = await this.getOrCreateSandbox(params.sandboxId); + const { sandbox } = await this.getOrCreateSandbox({ sandboxId: params.sandboxId }); const signed = await sandbox.getSignedPreviewUrl(params.port, params.expiresInSeconds); return signed.url; });