Skip to content

Commit 1463bbd

Browse files
Make Daytona sandbox lifecycle policies configurable and stop hardcoding auto-stop/delete values, while right-sizing standard Blacksmith CI jobs to 2 vCPU to lower cost for this lightweight workload.
1 parent 61ad673 commit 1463bbd

File tree

6 files changed

+139
-22
lines changed

6 files changed

+139
-22
lines changed

.github/workflows/check.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ env:
1717
jobs:
1818
Check:
1919
name: Check
20-
runs-on: blacksmith-4vcpu-ubuntu-2404
20+
runs-on: blacksmith-2vcpu-ubuntu-2404
2121
steps:
2222
- name: Checkout
2323
uses: actions/checkout@v4
@@ -47,7 +47,7 @@ jobs:
4747
4848
BuildPackage:
4949
name: Build Package Artifact
50-
runs-on: blacksmith-4vcpu-ubuntu-2404
50+
runs-on: blacksmith-2vcpu-ubuntu-2404
5151
needs: Check
5252
steps:
5353
- name: Checkout
@@ -84,7 +84,7 @@ jobs:
8484

8585
PackageE2E:
8686
name: Package Install E2E
87-
runs-on: blacksmith-4vcpu-ubuntu-2404
87+
runs-on: blacksmith-2vcpu-ubuntu-2404
8888
needs: BuildPackage
8989
steps:
9090
- name: Setup Node

.github/workflows/publish-package.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ permissions:
1616
jobs:
1717
resolve-merge-context:
1818
name: Resolve Merge Context
19-
runs-on: blacksmith-4vcpu-ubuntu-2404
19+
runs-on: blacksmith-2vcpu-ubuntu-2404
2020
permissions:
2121
contents: read
2222
pull-requests: read
@@ -122,7 +122,7 @@ jobs:
122122
123123
publish-and-manage-bump:
124124
name: Publish Package + Manage Bump PR
125-
runs-on: blacksmith-4vcpu-ubuntu-2404
125+
runs-on: blacksmith-2vcpu-ubuntu-2404
126126
needs: resolve-merge-context
127127
permissions:
128128
contents: write

.github/workflows/validate-pr-title.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ permissions:
1010
jobs:
1111
ValidatePrTitle:
1212
name: ValidatePrTitle
13-
runs-on: blacksmith-4vcpu-ubuntu-2404
13+
runs-on: blacksmith-2vcpu-ubuntu-2404
1414
steps:
1515
- name: Validate pull request title
1616
env:

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,15 @@ Useful `start` flags:
160160
bun run start -- --port 3000 --target us --sandbox-name opencode-dev
161161
bun run start -- --keep-sandbox
162162
bun run start -- --no-open
163+
bun run start -- --auto-stop-interval 60
164+
bun run start -- --auto-stop-interval 0 --auto-delete-interval -1
163165
```
164166

167+
`start` and `analyze` also honor these env vars when flags are omitted:
168+
169+
- `DAYTONA_AUTO_STOP_INTERVAL` (minutes, `0` disables auto-stop)
170+
- `DAYTONA_AUTO_DELETE_INTERVAL` (minutes, `-1` disables auto-delete)
171+
165172
---
166173

167174
## Repository Audit Workflow
@@ -195,6 +202,7 @@ bun run analyze -- https://github.com/owner/repo-one https://github.com/owner/re
195202
bun run analyze -- --out-dir findings --model zai-coding-plan/glm-4.7-flash --target us
196203
bun run analyze -- --vision
197204
bun run analyze -- --analyze-timeout-sec 3600 --keep-sandbox
205+
bun run analyze -- --auto-stop-interval 30 --auto-delete-interval -1 --input example.md
198206
```
199207

200208
If no URLs and no `--input` are provided, the script uses `example.md` when it exists.

src/analyze-repos.ts

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ type CliOptions = {
1414
createTimeoutSec: number;
1515
installTimeoutSec: number;
1616
analyzeTimeoutSec: number;
17+
autoStopInterval?: number;
18+
autoDeleteInterval?: number;
1719
keepSandbox: boolean;
1820
target?: string;
1921
model?: string;
@@ -52,6 +54,32 @@ function parsePositiveInt(value: string, flag: string): number {
5254
return parsed;
5355
}
5456

57+
function parseAutoStopInterval(value: string | undefined, source: string): number | undefined {
58+
if (value === undefined) {
59+
return undefined;
60+
}
61+
const parsed = Number.parseInt(value, 10);
62+
if (!Number.isInteger(parsed) || parsed < 0) {
63+
throw new Error(
64+
`${source} must be a non-negative integer (0 disables auto-stop). Received "${value}".`,
65+
);
66+
}
67+
return parsed;
68+
}
69+
70+
function parseAutoDeleteInterval(value: string | undefined, source: string): number | undefined {
71+
if (value === undefined) {
72+
return undefined;
73+
}
74+
const parsed = Number.parseInt(value, 10);
75+
if (!Number.isInteger(parsed) || (parsed !== -1 && parsed <= 0)) {
76+
throw new Error(
77+
`${source} must be -1 (disable auto-delete) or a positive integer. Received "${value}".`,
78+
);
79+
}
80+
return parsed;
81+
}
82+
5583
function requireEnv(name: string): string {
5684
const value = process.env[name];
5785
if (!value) {
@@ -178,6 +206,8 @@ function parseCliOptions(): CliOptions {
178206
"create-timeout-sec": { type: "string", default: "180" },
179207
"install-timeout-sec": { type: "string", default: "900" },
180208
"analyze-timeout-sec": { type: "string", default: "2400" },
209+
"auto-stop-interval": { type: "string" },
210+
"auto-delete-interval": { type: "string" },
181211
"keep-sandbox": { type: "boolean", default: false },
182212
target: { type: "string" },
183213
model: { type: "string" },
@@ -203,6 +233,8 @@ Options:
203233
--create-timeout-sec <n> Sandbox creation timeout (default: 180)
204234
--install-timeout-sec <n> OpenCode install timeout (default: 900)
205235
--analyze-timeout-sec <n> Per-repo analysis timeout (default: 2400)
236+
--auto-stop-interval <n> Daytona auto-stop interval (minutes, 0 disables)
237+
--auto-delete-interval <n> Daytona auto-delete interval (minutes, -1 disables)
206238
--target <name> Daytona target override
207239
--model <provider/model> OpenCode model (default: zai-coding-plan/glm-4.7-flash)
208240
--variant <name> Model variant (example: xhigh)
@@ -213,12 +245,24 @@ Options:
213245
process.exit(0);
214246
}
215247

248+
const autoStopInterval =
249+
parseAutoStopInterval(values["auto-stop-interval"], "--auto-stop-interval") ??
250+
parseAutoStopInterval(process.env.DAYTONA_AUTO_STOP_INTERVAL, "DAYTONA_AUTO_STOP_INTERVAL");
251+
const autoDeleteInterval =
252+
parseAutoDeleteInterval(values["auto-delete-interval"], "--auto-delete-interval") ??
253+
parseAutoDeleteInterval(
254+
process.env.DAYTONA_AUTO_DELETE_INTERVAL,
255+
"DAYTONA_AUTO_DELETE_INTERVAL",
256+
);
257+
216258
return {
217259
inputFile: values.input,
218260
outDir: values["out-dir"],
219261
createTimeoutSec: parsePositiveInt(values["create-timeout-sec"], "--create-timeout-sec"),
220262
installTimeoutSec: parsePositiveInt(values["install-timeout-sec"], "--install-timeout-sec"),
221263
analyzeTimeoutSec: parsePositiveInt(values["analyze-timeout-sec"], "--analyze-timeout-sec"),
264+
autoStopInterval,
265+
autoDeleteInterval,
222266
keepSandbox: values["keep-sandbox"],
223267
target: values.target,
224268
model: values.model,
@@ -615,14 +659,25 @@ async function analyzeOneRepo(params: {
615659
const sandboxName = sanitizeSlug(
616660
`audit-${slug}-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
617661
).slice(0, 63);
618-
sandbox = await daytona.create(
619-
{
620-
name: sandboxName,
621-
language: "typescript",
622-
autoStopInterval: 0,
623-
},
624-
{ timeout: options.createTimeoutSec },
625-
);
662+
663+
const createParams: {
664+
name: string;
665+
language: "typescript";
666+
autoStopInterval?: number;
667+
autoDeleteInterval?: number;
668+
} = {
669+
name: sandboxName,
670+
language: "typescript",
671+
};
672+
673+
if (options.autoStopInterval !== undefined) {
674+
createParams.autoStopInterval = options.autoStopInterval;
675+
}
676+
if (options.autoDeleteInterval !== undefined) {
677+
createParams.autoDeleteInterval = options.autoDeleteInterval;
678+
}
679+
680+
sandbox = await daytona.create(createParams, { timeout: options.createTimeoutSec });
626681
console.log(`[analyze] (${runPrefix}) Sandbox ready: ${sandbox.id}`);
627682

628683
const userHome = (await sandbox.getUserHomeDir()) ?? "/home/daytona";

src/start-opencode-daytona.ts

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type CliOptions = {
1313
sandboxName?: string;
1414
createTimeoutSec: number;
1515
installTimeoutSec: number;
16+
autoStopInterval?: number;
17+
autoDeleteInterval?: number;
1618
target?: string;
1719
openUi: boolean;
1820
};
@@ -45,6 +47,32 @@ function parsePort(value: string): number {
4547
return parsed;
4648
}
4749

50+
function parseAutoStopInterval(value: string | undefined, source: string): number | undefined {
51+
if (value === undefined) {
52+
return undefined;
53+
}
54+
const parsed = Number.parseInt(value, 10);
55+
if (!Number.isInteger(parsed) || parsed < 0) {
56+
throw new Error(
57+
`${source} must be a non-negative integer (0 disables auto-stop). Received "${value}".`,
58+
);
59+
}
60+
return parsed;
61+
}
62+
63+
function parseAutoDeleteInterval(value: string | undefined, source: string): number | undefined {
64+
if (value === undefined) {
65+
return undefined;
66+
}
67+
const parsed = Number.parseInt(value, 10);
68+
if (!Number.isInteger(parsed) || (parsed !== -1 && parsed <= 0)) {
69+
throw new Error(
70+
`${source} must be -1 (disable auto-delete) or a positive integer. Received "${value}".`,
71+
);
72+
}
73+
return parsed;
74+
}
75+
4876
function parseCliOptions(): CliOptions {
4977
const { values } = parseArgs({
5078
options: {
@@ -54,6 +82,8 @@ function parseCliOptions(): CliOptions {
5482
"sandbox-name": { type: "string" },
5583
"create-timeout-sec": { type: "string", default: "180" },
5684
"install-timeout-sec": { type: "string", default: "900" },
85+
"auto-stop-interval": { type: "string" },
86+
"auto-delete-interval": { type: "string" },
5787
target: { type: "string" },
5888
"no-open": { type: "boolean", default: false },
5989
},
@@ -70,19 +100,33 @@ Options:
70100
--sandbox-name <name> Custom sandbox name
71101
--create-timeout-sec <n> Sandbox creation timeout seconds (default: 180)
72102
--install-timeout-sec <n> OpenCode install timeout seconds (default: 900)
103+
--auto-stop-interval <n> Daytona auto-stop interval (minutes, 0 disables)
104+
--auto-delete-interval <n> Daytona auto-delete interval (minutes, -1 disables)
73105
--keep-sandbox Keep sandbox after stopping (default: false)
74106
--no-open Do not auto-open OpenCode URL
75107
-h, --help Show this help
76108
`);
77109
process.exit(0);
78110
}
79111

112+
const autoStopInterval =
113+
parseAutoStopInterval(values["auto-stop-interval"], "--auto-stop-interval") ??
114+
parseAutoStopInterval(process.env.DAYTONA_AUTO_STOP_INTERVAL, "DAYTONA_AUTO_STOP_INTERVAL");
115+
const autoDeleteInterval =
116+
parseAutoDeleteInterval(values["auto-delete-interval"], "--auto-delete-interval") ??
117+
parseAutoDeleteInterval(
118+
process.env.DAYTONA_AUTO_DELETE_INTERVAL,
119+
"DAYTONA_AUTO_DELETE_INTERVAL",
120+
);
121+
80122
return {
81123
port: parsePort(values.port),
82124
keepSandbox: values["keep-sandbox"],
83125
sandboxName: values["sandbox-name"],
84126
createTimeoutSec: parsePositiveInt(values["create-timeout-sec"], "--create-timeout-sec"),
85127
installTimeoutSec: parsePositiveInt(values["install-timeout-sec"], "--install-timeout-sec"),
128+
autoStopInterval,
129+
autoDeleteInterval,
86130
target: values.target,
87131
openUi: !values["no-open"],
88132
};
@@ -410,14 +454,24 @@ async function main(): Promise<void> {
410454

411455
try {
412456
console.log("[local] Creating Daytona sandbox...");
413-
sandbox = await daytona.create(
414-
{
415-
name: options.sandboxName,
416-
language: "typescript",
417-
autoStopInterval: 0,
418-
},
419-
{ timeout: options.createTimeoutSec },
420-
);
457+
const createParams: {
458+
name?: string;
459+
language: "typescript";
460+
autoStopInterval?: number;
461+
autoDeleteInterval?: number;
462+
} = {
463+
name: options.sandboxName,
464+
language: "typescript",
465+
};
466+
467+
if (options.autoStopInterval !== undefined) {
468+
createParams.autoStopInterval = options.autoStopInterval;
469+
}
470+
if (options.autoDeleteInterval !== undefined) {
471+
createParams.autoDeleteInterval = options.autoDeleteInterval;
472+
}
473+
474+
sandbox = await daytona.create(createParams, { timeout: options.createTimeoutSec });
421475
console.log(`[local] Sandbox ready: ${sandbox.id}`);
422476

423477
const userHome = (await sandbox.getUserHomeDir()) ?? "/home/daytona";

0 commit comments

Comments
 (0)