Skip to content

Commit b8ec4ff

Browse files
NagyViktNagyViktclaude
authored
feat(agents): cap CARGO_BUILD_JOBS in agent launch env (#601)
Prepend CARGO_BUILD_JOBS=max(2, floor(cpus/4)) to every agent launch command so concurrent fleet runs don't oversubscribe the host when child cargo builds fan out. Harmless on non-Rust agents — cargo is the only consumer of the env var. Tests previously hardcoded CARGO_BUILD_JOBS=8 (passed only on 32-CPU hosts); they now compute the expected value from os.cpus() so CI on any CPU count stays green. Co-authored-by: NagyVikt <nagy.viktordp@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b61d65b commit b8ec4ff

6 files changed

Lines changed: 70 additions & 16 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-17
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# agent-claude-cap-cargo-jobs-in-agent-launch-env-2026-05-17-21-02 (minimal / T1)
2+
3+
Branch: `agent/claude/cap-cargo-jobs-in-agent-launch-env-2026-05-17-21-02`
4+
5+
Cap `CARGO_BUILD_JOBS` in the env prefix of every agent launch command so concurrent agent runs don't oversubscribe the host when child cargo builds fan out. Formula: `max(2, floor(os.cpus().length / 4))`. Harmless on non-Rust agents (env var is only read by cargo).
6+
7+
## Files
8+
9+
- `src/agents/launch.js` — new `buildResourceEnv()`, prepended to `buildSessionEnv()` in `buildAgentLaunchCommand`.
10+
- `test/agents-launch.test.js` — expected `CARGO_BUILD_JOBS=<n>` is computed from `os.cpus()` (was hardcoded `=8`, only passed on 32-CPU hosts). Added a coverage test asserting the formula.
11+
- `test/agents-start-dry-run.test.js` — regex relaxed to `CARGO_BUILD_JOBS=\d+`.
12+
- `test/agents-start.test.js` — canonical-session `launchCommand` assertions updated to use the computed `CARGO` prefix.
13+
14+
## Verification
15+
16+
- `node --test test/agents-launch.test.js test/agents-start-dry-run.test.js test/agents-start.test.js` — 24/24 pass.
17+
- `npm test` — 570 pass / 23 fail; failure count identical to clean `main` baseline (pre-existing, unrelated).
18+
19+
## Handoff
20+
21+
- Handoff: change=`agent-claude-cap-cargo-jobs-in-agent-launch-env-2026-05-17-21-02`; branch=`agent/claude/cap-cargo-jobs-in-agent-launch-env-2026-05-17-21-02`; scope=`cap CARGO_BUILD_JOBS in agent launch env`; action=`finish cleanup`.
22+
23+
## Cleanup
24+
25+
- [ ] Run: `gx branch finish --branch agent/claude/cap-cargo-jobs-in-agent-launch-env-2026-05-17-21-02 --base main --via-pr --wait-for-merge --cleanup`
26+
- [ ] Record PR URL + `MERGED` state in the completion handoff.
27+
- [ ] Confirm sandbox worktree is gone (`git worktree list`, `git branch -a`).

src/agents/launch.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ function buildPromptCommand(parts, agent, prompt) {
185185
return commandToShell([...parts, prompt]);
186186
}
187187

188+
function buildResourceEnv() {
189+
const cpus = require('node:os').cpus().length || 8;
190+
// Cap each agent's cargo parallelism to avoid overwhelming the system
191+
// when multiple agents build concurrently.
192+
const jobs = Math.max(2, Math.floor(cpus / 4));
193+
return [`CARGO_BUILD_JOBS=${jobs}`];
194+
}
195+
188196
function buildAgentLaunchCommand(options) {
189197
if (!options || typeof options !== 'object') {
190198
throw new TypeError('options are required');
@@ -207,7 +215,8 @@ function buildAgentLaunchCommand(options) {
207215
}
208216

209217
const launchCommand = buildPromptCommand(baseParts, agent, prompt);
210-
const envPrefix = buildSessionEnv(agent, sessionId).join(' ');
218+
const envParts = [...buildResourceEnv(), ...buildSessionEnv(agent, sessionId)];
219+
const envPrefix = envParts.join(' ');
211220
const launchWithEnv = envPrefix ? `${envPrefix} ${launchCommand}` : launchCommand;
212221
if (!worktreePath) return launchWithEnv;
213222
return `cd ${shellQuote(worktreePath)} && ${launchWithEnv}`;

test/agents-launch.test.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
const assert = require('node:assert/strict');
44
const test = require('node:test');
5+
const os = require('node:os');
56

67
const {
78
buildAgentLaunchCommand,
89
buildAgentResumeCommand,
910
} = require('../src/agents/launch');
1011

12+
const JOBS = Math.max(2, Math.floor((os.cpus().length || 8) / 4));
13+
const CARGO = `CARGO_BUILD_JOBS=${JOBS}`;
14+
1115
test('builds codex launch commands with positional prompts', () => {
1216
assert.equal(
1317
buildAgentLaunchCommand({
@@ -17,7 +21,7 @@ test('builds codex launch commands with positional prompts', () => {
1721
permissionMode: 'workspace-write',
1822
sessionId: 'session-1',
1923
}),
20-
"cd '/tmp/work tree' && OMX_SESSION_ID='session-1' 'codex' '--permission-mode' 'workspace-write' 'fix tests'",
24+
`cd '/tmp/work tree' && ${CARGO} OMX_SESSION_ID='session-1' 'codex' '--permission-mode' 'workspace-write' 'fix tests'`,
2125
);
2226
});
2327

@@ -28,7 +32,7 @@ test('builds claude launch commands with argument prompts', () => {
2832
prompt: 'review code',
2933
permissionMode: 'acceptEdits',
3034
}),
31-
"'claude' '--permission-mode' 'acceptEdits' 'review code'",
35+
`${CARGO} 'claude' '--permission-mode' 'acceptEdits' 'review code'`,
3236
);
3337
});
3438

@@ -38,7 +42,7 @@ test('builds opencode launch commands with positional prompts', () => {
3842
agentId: 'opencode',
3943
prompt: 'implement feature',
4044
}),
41-
"'opencode' 'implement feature'",
45+
`${CARGO} 'opencode' 'implement feature'`,
4246
);
4347
});
4448

@@ -49,7 +53,7 @@ test('builds cursor launch commands with argument prompts', () => {
4953
prompt: 'inspect current branch',
5054
worktreePath: '/repo/worktree',
5155
}),
52-
"cd '/repo/worktree' && 'cursor-agent' 'inspect current branch'",
56+
`cd '/repo/worktree' && ${CARGO} 'cursor-agent' 'inspect current branch'`,
5357
);
5458
});
5559

@@ -60,7 +64,7 @@ test('builds gemini launch commands with argument prompts', () => {
6064
prompt: 'summarize repo',
6165
sessionId: 'session-2',
6266
}),
63-
"OMX_SESSION_ID='session-2' 'gemini' 'summarize repo'",
67+
`${CARGO} OMX_SESSION_ID='session-2' 'gemini' 'summarize repo'`,
6468
);
6569
});
6670

@@ -69,27 +73,36 @@ test('quotes prompts with single quotes, newlines, and dollar signs safely', ()
6973

7074
assert.equal(
7175
buildAgentLaunchCommand({ agentId: 'codex', prompt }),
72-
"'codex' 'say '\\''hello'\\''\nthen echo $HOME'",
76+
`${CARGO} 'codex' 'say '\\''hello'\\''\nthen echo $HOME'`,
7377
);
7478

7579
assert.equal(
7680
buildAgentLaunchCommand({ agentId: 'gemini', prompt }),
77-
"'gemini' 'say '\\''hello'\\''\nthen echo $HOME'",
81+
`${CARGO} 'gemini' 'say '\\''hello'\\''\nthen echo $HOME'`,
7882
);
7983

8084
assert.equal(
8185
buildAgentLaunchCommand({ agentId: 'cursor', prompt }),
82-
"'cursor-agent' 'say '\\''hello'\\''\nthen echo $HOME'",
86+
`${CARGO} 'cursor-agent' 'say '\\''hello'\\''\nthen echo $HOME'`,
8387
);
8488
});
8589

8690
test('omits prompts when none are supplied', () => {
8791
assert.equal(
8892
buildAgentLaunchCommand({ agentId: 'codex', worktreePath: '/repo' }),
89-
"cd '/repo' && 'codex'",
93+
`cd '/repo' && ${CARGO} 'codex'`,
9094
);
9195
});
9296

97+
test('caps CARGO_BUILD_JOBS at floor(cpus/4), minimum 2', () => {
98+
const cmd = buildAgentLaunchCommand({ agentId: 'codex', prompt: 'x' });
99+
const match = cmd.match(/CARGO_BUILD_JOBS=(\d+)/);
100+
assert.ok(match, 'CARGO_BUILD_JOBS env var must be present');
101+
const jobs = Number(match[1]);
102+
assert.ok(jobs >= 2, `expected jobs >= 2, got ${jobs}`);
103+
assert.equal(jobs, JOBS);
104+
});
105+
93106
test('builds resume commands for supported agents', () => {
94107
assert.equal(buildAgentResumeCommand('codex', 'workspace-write'), "'codex' 'resume' '--permission-mode' 'workspace-write'");
95108
assert.equal(buildAgentResumeCommand('claude'), "'claude' '--continue'");

test/agents-start-dry-run.test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ test('gx agents start dry-run prints the planned codex branch, worktree, and lau
3131
result.stdout,
3232
new RegExp(`worktree: ${repoDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/\\.omx/agent-worktrees/[^\\n]*codex__fix-auth-tests-2026-04-29-21-30`),
3333
);
34-
assert.match(result.stdout, /launch: cd '.*' && 'codex' 'fix auth tests'/);
34+
assert.match(result.stdout, /launch: cd '.*' && CARGO_BUILD_JOBS=\d+ 'codex' 'fix auth tests'/);
3535
assert.match(result.stdout, /No branch, worktree, session metadata, or agent process was created\./);
3636

3737
const branchCheck = runCmd(
@@ -64,7 +64,7 @@ test('gx agents start dry-run supports claude worktree planning and rejects unkn
6464
assert.equal(claudeResult.status, 0, claudeResult.stderr || claudeResult.stdout);
6565
assert.match(claudeResult.stdout, /branch: agent\/claude\/update-docs-2026-04-29-21-31/);
6666
assert.match(claudeResult.stdout, /\.omc\/agent-worktrees\/[^ \n]*claude__update-docs-2026-04-29-21-31/);
67-
assert.match(claudeResult.stdout, /launch: cd '.*' && 'claude' 'update docs'/);
67+
assert.match(claudeResult.stdout, /launch: cd '.*' && CARGO_BUILD_JOBS=\d+ 'claude' 'update docs'/);
6868

6969
const invalidResult = runNode(
7070
['agents', 'start', 'update docs', '--agent', 'bogus', '--dry-run'],
@@ -164,7 +164,7 @@ test('gx agents start --dry-run --json emits Colony-ready launch plan', () => {
164164
assert.equal(payload.branch, 'agent/codex/colony-dry-run-2026-04-30-00-05');
165165
assert.match(payload.worktree, /\.omx\/agent-worktrees\/repo__codex__colony-dry-run-2026-04-30-00-05$/);
166166
assert.deepEqual(payload.claimedFiles, ['README.md']);
167-
assert.match(payload.launchCommand, /cd '.*' && 'codex' 'colony dry run'/);
167+
assert.match(payload.launchCommand, /cd '.*' && CARGO_BUILD_JOBS=\d+ 'codex' 'colony dry run'/);
168168
assert.equal(payload.tmuxSession, null);
169169
assert.equal(payload.tmuxTarget, null);
170170
assert.deepEqual(payload.metadata, {
@@ -300,5 +300,5 @@ test('interactive launcher panel asks for a task when opened empty', () => {
300300
const output = stdout.chunks.join('');
301301
assert.match(output, /task: fix auth/);
302302
assert.match(output, /branch: agent\/codex\/fix-auth-/);
303-
assert.match(output, /launch: cd '.*' && 'codex' 'fix auth'/);
303+
assert.match(output, /launch: cd '.*' && CARGO_BUILD_JOBS=\d+ 'codex' 'fix auth'/);
304304
});

test/agents-start.test.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const fs = require('node:fs');
55
const os = require('node:os');
66
const path = require('node:path');
77

8+
const CARGO_JOBS = Math.max(2, Math.floor((os.cpus().length || 8) / 4));
9+
const CARGO = `CARGO_BUILD_JOBS=${CARGO_JOBS}`;
10+
811
function loadStartWithMocks({
912
runPackageAsset,
1013
createAgentSession,
@@ -95,7 +98,7 @@ test('agents start creates canonical session after successful branch start', ()
9598
base: 'main',
9699
claims: [],
97100
metadata: {},
98-
launchCommand: "cd '/repo/.omx/agent-worktrees/repo__codex__fix-auth' && 'codex' 'fix auth'",
101+
launchCommand: `cd '/repo/.omx/agent-worktrees/repo__codex__fix-auth' && ${CARGO} 'codex' 'fix auth'`,
99102
tmux: null,
100103
status: 'active',
101104
},
@@ -183,7 +186,7 @@ test('agents start claim failure updates canonical session to claim-failed', ()
183186
base: 'main',
184187
claims: ['src/auth.js'],
185188
metadata: {},
186-
launchCommand: "cd '/repo/.omx/agent-worktrees/repo__codex__fix-auth' && 'codex' 'fix auth'",
189+
launchCommand: `cd '/repo/.omx/agent-worktrees/repo__codex__fix-auth' && ${CARGO} 'codex' 'fix auth'`,
187190
tmux: null,
188191
status: 'claim-failed',
189192
claimFailure: {

0 commit comments

Comments
 (0)