claude-code-session-manager 0.124.1 → 0.125.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AgentLibrary-qmAgadiS.js → AgentLibrary-CcnnJg8P.js} +1 -1
- package/dist/assets/{Button-CkuaU6cA.js → Button-sA4lqAao.js} +1 -1
- package/dist/assets/{DataModel-DEVHxW5I.js → DataModel-BSTejIBU.js} +1 -1
- package/dist/assets/{History-C-qw4Gx4.js → History-DuwlZsQ5.js} +1 -1
- package/dist/assets/{Hooks-DzAnCyjJ.js → Hooks-DHDSoDJ_.js} +1 -1
- package/dist/assets/{Library-DExZ29wM.js → Library-XA9osRfi.js} +1 -1
- package/dist/assets/{ListDetail-DiQk-Hf9.js → ListDetail-B3goE3Wc.js} +1 -1
- package/dist/assets/{McpServers-C0nqX36_.js → McpServers-cMZTsPS6.js} +1 -1
- package/dist/assets/{Memory-D4KAWPAC.js → Memory-D6qntpZ0.js} +1 -1
- package/dist/assets/{Panel-xv9ToSjl.js → Panel-DIyJXHfp.js} +1 -1
- package/dist/assets/{Permissions-0yEyqwZU.js → Permissions-DJda4Aii.js} +1 -1
- package/dist/assets/{Plugins-CB28SGGJ.js → Plugins-DcdRVBnU.js} +2 -2
- package/dist/assets/{ProvenanceBadge-9i4gXDvI.js → ProvenanceBadge-BSI_2wAv.js} +1 -1
- package/dist/assets/{SaveBar-mSVRXpml.js → SaveBar-CeL4vXRp.js} +1 -1
- package/dist/assets/{Scheduler-BoUetIC0.js → Scheduler-CTd19y5U.js} +1 -1
- package/dist/assets/{ScopeSwitcher-B8tdQIKs.js → ScopeSwitcher-BMiHySnz.js} +1 -1
- package/dist/assets/{Settings-D3jfKwaK.js → Settings-D-oIEKZy.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-DJ4JJRt3.js → SkillReferenceGraph-B_7WfFjn.js} +1 -1
- package/dist/assets/{Skills-P9HHxDCR.js → Skills-CKpgxhSS.js} +1 -1
- package/dist/assets/{SystemPrompt-B_tnbBZ2.js → SystemPrompt-BjJPTx_a.js} +1 -1
- package/dist/assets/{TagLibrary-CI8NHRKi.js → TagLibrary-DJBB4gL0.js} +1 -1
- package/dist/assets/{TiptapBody-9LktXPiD.js → TiptapBody-C9Cu2GBL.js} +1 -1
- package/dist/assets/{Toggle-BwsUsAA7.js → Toggle-DbIRTOix.js} +1 -1
- package/dist/assets/{ViewTabs-CY5gMZ_N.js → ViewTabs-BDpHur6l.js} +1 -1
- package/dist/assets/{historyTotals-_dIaxmPX.js → historyTotals-WCJHsBMq.js} +1 -1
- package/dist/assets/{index-d4i3X1x3.js → index-BD0ufZqe.js} +3 -3
- package/dist/assets/{settingsSchema-Dg0Xa9RR.js → settingsSchema-DmjBj3q7.js} +1 -1
- package/dist/assets/{skillFrontmatter-CpoFkSyj.js → skillFrontmatter-Tn_XHajE.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-CFPprfWG.js → useAgentsDirWatch-Vz2iXtsQ.js} +1 -1
- package/dist/index.html +1 -1
- package/dist-web/assets/app.js +1 -1
- package/package.json +1 -1
- package/src/main/build-info.json +4 -4
- package/src/main/chatRunner.cjs +11 -5
- package/src/main/docEdit.cjs +8 -4
- package/src/main/ipcSchemas.cjs +4 -1
- package/src/main/lib/classifyPromptTicket.cjs +9 -4
- package/src/main/lib/claude/runClaudeP.cjs +7 -4
- package/src/main/lib/process/childStdin.cjs +39 -0
- package/src/main/lib/process/childWithLog.cjs +15 -1
- package/src/main/lib/scheduler/launchFailure.cjs +37 -1
- package/src/main/scheduler.cjs +111 -30
- package/src/main/supervisor.cjs +6 -2
|
@@ -57,6 +57,38 @@ const LAUNCH_FAILURE_KINDS = Object.freeze({
|
|
|
57
57
|
API_ERROR: 'api_error',
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Argv-size guard (incident 2026-10-26, social-signals-trader): an oversized
|
|
62
|
+
* `-p` argument made `spawn` fail with E2BIG (exit -1 in ~86 ms), which
|
|
63
|
+
* became `impl_failed:exit_-1` and spawned a fix-plan that failed the same
|
|
64
|
+
* way. Linux caps ONE argv string at MAX_ARG_STRLEN = 131072 bytes, counting
|
|
65
|
+
* the NUL terminator. The cause is one job's input, not the persona's
|
|
66
|
+
* environment, so this never arms the per-persona launch breaker.
|
|
67
|
+
*/
|
|
68
|
+
const MAX_ARG_BYTES = 131072;
|
|
69
|
+
|
|
70
|
+
/** First argv element whose utf8 bytes + NUL exceed `limit`, else null. */
|
|
71
|
+
function findOversizedArg(args, limit = MAX_ARG_BYTES) {
|
|
72
|
+
if (!Array.isArray(args)) return null;
|
|
73
|
+
for (let index = 0; index < args.length; index++) {
|
|
74
|
+
const a = args[index];
|
|
75
|
+
if (typeof a !== 'string') continue;
|
|
76
|
+
const bytes = Buffer.byteLength(a, 'utf8') + 1;
|
|
77
|
+
if (bytes <= limit) continue;
|
|
78
|
+
let flag = '(positional)';
|
|
79
|
+
for (let j = index - 1; j >= 0; j--) {
|
|
80
|
+
if (typeof args[j] === 'string' && args[j].startsWith('--')) { flag = args[j]; break; }
|
|
81
|
+
}
|
|
82
|
+
return { index, flag, bytes };
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** True when a spawn error message is the OS refusing an oversized argv. */
|
|
88
|
+
function isArgvTooLargeSpawnError(message) {
|
|
89
|
+
return /E2BIG|ENAMETOOLONG/.test(String(message ?? ''));
|
|
90
|
+
}
|
|
91
|
+
|
|
60
92
|
/** Tail bytes scanned for the `result` event — same budget classifyRunOutcome uses. */
|
|
61
93
|
const RESULT_TAIL_BYTES = 65536;
|
|
62
94
|
|
|
@@ -310,9 +342,10 @@ function evaluateLaunchGate(block, { now, claudeVersion } = {}) {
|
|
|
310
342
|
* closed set so operators never have to open a transcript to tell a
|
|
311
343
|
* non-start from an implementation failure from a verifier downgrade.
|
|
312
344
|
*/
|
|
313
|
-
function deriveTerminalReason({ effectiveStatus, exitCode, verifyResult, sigtermOverride, worktreeIntegrationFailure, budgetKill }) {
|
|
345
|
+
function deriveTerminalReason({ effectiveStatus, exitCode, verifyResult, sigtermOverride, worktreeIntegrationFailure, budgetKill, launchError }) {
|
|
314
346
|
if (worktreeIntegrationFailure) return 'worktree_integration_failed';
|
|
315
347
|
if (effectiveStatus === 'completed') return 'completed';
|
|
348
|
+
if (launchError === 'argv_too_large') return 'launch_failure:argv_too_large';
|
|
316
349
|
if (budgetKill) return 'budget_exceeded';
|
|
317
350
|
if (sigtermOverride) return 'signal_kill_with_commit';
|
|
318
351
|
if (exitCode === 143 || exitCode === 137) return 'signal_kill';
|
|
@@ -338,6 +371,9 @@ function writeOutcomeSidecar(runDir, slug, outcome) {
|
|
|
338
371
|
}
|
|
339
372
|
|
|
340
373
|
module.exports = {
|
|
374
|
+
MAX_ARG_BYTES,
|
|
375
|
+
findOversizedArg,
|
|
376
|
+
isArgvTooLargeSpawnError,
|
|
341
377
|
LAUNCH_FAILURE_KINDS,
|
|
342
378
|
LAUNCH_BLOCK_MAX_ATTEMPTS,
|
|
343
379
|
LAUNCH_PROBE_STALE_MS,
|
package/src/main/scheduler.cjs
CHANGED
|
@@ -3474,7 +3474,9 @@ const TRANSIENT_RETRY_CAP = 2;
|
|
|
3474
3474
|
* transient (PRD 543/545 incident) — same bounded-retry treatment, not a
|
|
3475
3475
|
* second parallel classifier.
|
|
3476
3476
|
*/
|
|
3477
|
-
function classifyFailureOutcome({ exitCode, networkError, durationMs, transientRetries, newlyDirtyCount }) {
|
|
3477
|
+
function classifyFailureOutcome({ exitCode, networkError, durationMs, transientRetries, newlyDirtyCount, launchError }) {
|
|
3478
|
+
// The run never started (argv too large): not transient, not investigable.
|
|
3479
|
+
if (launchError) return { action: 'fail-launch', launchError };
|
|
3478
3480
|
const signalTransient = (exitCode === 143 || exitCode === 137) && durationMs < IDLE_OUTPUT_KILL_MS;
|
|
3479
3481
|
const networkTransient = networkError === true;
|
|
3480
3482
|
const transient = signalTransient || networkTransient;
|
|
@@ -3677,37 +3679,87 @@ function capDirtyPaths(paths, cap = PRE_RUN_DIRTY_PATHS_CAP) {
|
|
|
3677
3679
|
const FOREIGN_WIP_DELIMITER = '--- FOREIGN WORKING-TREE STATE (not your work) ---';
|
|
3678
3680
|
const FOREIGN_WIP_END_DELIMITER = '--- END FOREIGN WORKING-TREE STATE ---';
|
|
3679
3681
|
|
|
3682
|
+
const FOREIGN_WIP_SECTION_MAX_BYTES = 16 * 1024;
|
|
3683
|
+
const FOREIGN_WIP_SUMMARY_RESERVE_BYTES = 1024;
|
|
3684
|
+
const FOREIGN_WIP_OMITTED_SENTENCE = 'The omitted paths are just as foreign: do not stage, commit, revert, or stash them either.';
|
|
3685
|
+
|
|
3686
|
+
/**
|
|
3687
|
+
* Pure: bound a foreign-WIP path list by count (PRE_RUN_DIRTY_PATHS_CAP) and
|
|
3688
|
+
* bytes. A trailing capDirtyPaths `+N more` marker is never listed; its N is
|
|
3689
|
+
* added to omittedCount. Omitted real paths are grouped by parent directory.
|
|
3690
|
+
* `fixedBytes` = byte length of the section's fixed sentences/delimiters.
|
|
3691
|
+
*/
|
|
3692
|
+
function limitForeignWipPaths(paths, fixedBytes = 0) {
|
|
3693
|
+
const all = Array.isArray(paths) ? paths.filter(Boolean) : [];
|
|
3694
|
+
let omittedCount = 0;
|
|
3695
|
+
if (all.length && /^\+\d+ more$/.test(all[all.length - 1])) {
|
|
3696
|
+
omittedCount += Number(all.pop().slice(1).split(' ')[0]);
|
|
3697
|
+
}
|
|
3698
|
+
const budget = FOREIGN_WIP_SECTION_MAX_BYTES - fixedBytes - FOREIGN_WIP_SUMMARY_RESERVE_BYTES;
|
|
3699
|
+
const listed = [];
|
|
3700
|
+
let used = 0;
|
|
3701
|
+
let i = 0;
|
|
3702
|
+
for (; i < all.length && listed.length < PRE_RUN_DIRTY_PATHS_CAP; i++) {
|
|
3703
|
+
const lineBytes = Buffer.byteLength(` ${all[i]}\n`, 'utf8');
|
|
3704
|
+
if (used + lineBytes > budget) break;
|
|
3705
|
+
used += lineBytes;
|
|
3706
|
+
listed.push(all[i]);
|
|
3707
|
+
}
|
|
3708
|
+
const byDir = new Map();
|
|
3709
|
+
for (; i < all.length; i++) {
|
|
3710
|
+
const slash = all[i].lastIndexOf('/');
|
|
3711
|
+
const dir = slash >= 0 ? all[i].slice(0, slash + 1) : '(repo root)';
|
|
3712
|
+
byDir.set(dir, (byDir.get(dir) || 0) + 1);
|
|
3713
|
+
omittedCount += 1;
|
|
3714
|
+
}
|
|
3715
|
+
const omittedByDir = [...byDir.entries()].sort((x, y) => y[1] - x[1]);
|
|
3716
|
+
return { listed, omittedCount, omittedByDir };
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
function foreignWipSummaryLine(omittedCount, omittedByDir) {
|
|
3720
|
+
const fmt = (n) => n.toLocaleString('en-US');
|
|
3721
|
+
const clip = (d) => (d.length > 120 ? `${d.slice(0, 117)}...` : d);
|
|
3722
|
+
const top = omittedByDir.slice(0, 5);
|
|
3723
|
+
const parts = top.map(([d, n]) => `${fmt(n)} under ${clip(d)}`);
|
|
3724
|
+
const rest = omittedByDir.slice(5).reduce((sum, [, n]) => sum + n, 0);
|
|
3725
|
+
if (rest > 0) parts.push(`${fmt(rest)} elsewhere`);
|
|
3726
|
+
const tail = parts.length ? `: ${parts.join(', ')}` : '';
|
|
3727
|
+
return ` ... and ${fmt(omittedCount)} more path(s) not listed${tail}`;
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3680
3730
|
/**
|
|
3681
3731
|
* Pure: build the executor-prompt section warning about pre-existing dirty
|
|
3682
3732
|
* paths this job does not own — either base WIP carried into an isolated
|
|
3683
3733
|
* worktree (PRD 1094's carriedPaths, checked first since it's the more
|
|
3684
3734
|
* specific/authoritative case) or the raw pre-run dirty snapshot of a shared
|
|
3685
3735
|
* (non-isolated) tree. Returns '' when both lists are empty so a clean spawn
|
|
3686
|
-
* produces a byte-identical prompt to before this section existed.
|
|
3736
|
+
* produces a byte-identical prompt to before this section existed. Both
|
|
3737
|
+
* branches are bounded by count and bytes (FOREIGN_WIP_SECTION_MAX_BYTES).
|
|
3687
3738
|
*/
|
|
3688
3739
|
function buildForeignWipSection({ preRunDirtyPaths, carriedPaths } = {}) {
|
|
3689
3740
|
const carried = Array.isArray(carriedPaths) ? carriedPaths.filter(Boolean) : [];
|
|
3690
|
-
if (carried.length) {
|
|
3691
|
-
return [
|
|
3692
|
-
FOREIGN_WIP_DELIMITER,
|
|
3693
|
-
'This job is running in an isolated git worktree, but the following paths carry uncommitted base-tree work-in-progress that was carried into this checkout so the tree is self-consistent. The authoritative copy of these files lives in the MAIN tree, not this worktree.',
|
|
3694
|
-
'These files were already modified before this job started. They are NOT this job\'s work:',
|
|
3695
|
-
...carried.map((p) => ` ${p}`),
|
|
3696
|
-
'Do not stage, commit, revert, or stash these paths. A test failure confined to these paths is not this job\'s regression.',
|
|
3697
|
-
FOREIGN_WIP_END_DELIMITER,
|
|
3698
|
-
].join('\n');
|
|
3699
|
-
}
|
|
3700
3741
|
const dirty = Array.isArray(preRunDirtyPaths) ? preRunDirtyPaths.filter(Boolean) : [];
|
|
3701
|
-
if (dirty.length)
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3742
|
+
if (!carried.length && !dirty.length) return '';
|
|
3743
|
+
const isCarried = carried.length > 0;
|
|
3744
|
+
const head = isCarried
|
|
3745
|
+
? [
|
|
3746
|
+
FOREIGN_WIP_DELIMITER,
|
|
3747
|
+
'This job is running in an isolated git worktree, but the following paths carry uncommitted base-tree work-in-progress that was carried into this checkout so the tree is self-consistent. The authoritative copy of these files lives in the MAIN tree, not this worktree.',
|
|
3748
|
+
'These files were already modified before this job started. They are NOT this job\'s work:',
|
|
3749
|
+
]
|
|
3750
|
+
: [
|
|
3751
|
+
FOREIGN_WIP_DELIMITER,
|
|
3752
|
+
'This job is running in a SHARED working tree (not isolated in its own worktree). The following paths were already modified when this job started:',
|
|
3753
|
+
];
|
|
3754
|
+
const tailLines = isCarried
|
|
3755
|
+
? ['Do not stage, commit, revert, or stash these paths. A test failure confined to these paths is not this job\'s regression.', FOREIGN_WIP_END_DELIMITER]
|
|
3756
|
+
: ['These files are NOT this job\'s work. Do not stage, commit, revert, or stash them. A test failure confined to these paths is not this job\'s regression.', FOREIGN_WIP_END_DELIMITER];
|
|
3757
|
+
const fixedBytes = Buffer.byteLength([...head, ...tailLines, FOREIGN_WIP_OMITTED_SENTENCE].join('\n'), 'utf8') + 8;
|
|
3758
|
+
const { listed, omittedCount, omittedByDir } = limitForeignWipPaths(isCarried ? carried : dirty, fixedBytes);
|
|
3759
|
+
const omitted = omittedCount > 0
|
|
3760
|
+
? [foreignWipSummaryLine(omittedCount, omittedByDir), FOREIGN_WIP_OMITTED_SENTENCE]
|
|
3761
|
+
: [];
|
|
3762
|
+
return [...head, ...listed.map((p) => ` ${p}`), ...omitted, ...tailLines].join('\n');
|
|
3711
3763
|
}
|
|
3712
3764
|
|
|
3713
3765
|
// A job whose validated BLOCKED_BY_FOREIGN_WIP claim keeps recurring against
|
|
@@ -4157,11 +4209,15 @@ const HEADLESS_DISALLOWED_TOOLS = Object.freeze([
|
|
|
4157
4209
|
* inserted BEFORE that flag, never between `--disallowedTools` and its value.
|
|
4158
4210
|
* `--disallowedTools` takes a variadic list, so another flag must always
|
|
4159
4211
|
* follow it to end that list — `--output-format` does, right after. Never
|
|
4160
|
-
* put `--disallowedTools` last
|
|
4212
|
+
* put `--disallowedTools` last.
|
|
4213
|
+
* The prompt is NOT in argv: callers send it on the child's stdin
|
|
4214
|
+
* (withChildAndLog's `stdinText`). Linux caps a single argv element at 128 KB,
|
|
4215
|
+
* and a large prompt failed with spawn E2BIG before the job started (the
|
|
4216
|
+
* 2026-10-26 incident). `claude -p` with no positional prompt reads stdin.
|
|
4161
4217
|
*/
|
|
4162
|
-
function buildClaudeSpawnArgs({
|
|
4218
|
+
function buildClaudeSpawnArgs({ model, effort, sessionId, resume, systemPrompt, toolScope }) {
|
|
4163
4219
|
return [
|
|
4164
|
-
'-p',
|
|
4220
|
+
'-p',
|
|
4165
4221
|
'--model', model,
|
|
4166
4222
|
...effortArgs(effort),
|
|
4167
4223
|
...(systemPrompt ? ['--append-system-prompt', systemPrompt] : []),
|
|
@@ -4685,7 +4741,6 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
4685
4741
|
// Distinct comm (`sm-claude-job`) + slug-labelled argv0 for System Monitor.
|
|
4686
4742
|
// Both keep the word `claude`, which the /\bclaude\b/ reaper gates need.
|
|
4687
4743
|
const jobArgs = buildClaudeSpawnArgs({
|
|
4688
|
-
prompt,
|
|
4689
4744
|
model: personaResolution.model,
|
|
4690
4745
|
effort: personaEffort,
|
|
4691
4746
|
sessionId,
|
|
@@ -4695,6 +4750,17 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
4695
4750
|
});
|
|
4696
4751
|
const jobSpawn = claudeSpawnTarget('job', job.slug, claudeBin, jobArgs);
|
|
4697
4752
|
|
|
4753
|
+
// Pre-spawn argv guard: one oversized argument makes spawn throw E2BIG.
|
|
4754
|
+
const oversized = launchFailure.findOversizedArg(jobSpawn.args || jobArgs);
|
|
4755
|
+
if (oversized) {
|
|
4756
|
+
const errMsg = `argv too large: ${oversized.flag} is ${oversized.bytes} bytes, over the ${launchFailure.MAX_ARG_BYTES}-byte per-argument limit; not spawned`;
|
|
4757
|
+
safeLog(`[scheduler] ${errMsg}\n`);
|
|
4758
|
+
closeFd();
|
|
4759
|
+
config.writeJsonSync(metaPath, { slug: job.slug, cwd, sessionId, exitCode: -1, error: errMsg, startedAt, finishedAt: Date.now(), durationMs: 0, ...SCHEDULER_META_IDENTITY, ...setupMeta });
|
|
4760
|
+
resolve({ exitCode: -1, durationMs: 0, error: errMsg, launchError: 'argv_too_large', sessionId });
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4698
4764
|
const { child } = withChildAndLog({
|
|
4699
4765
|
fd,
|
|
4700
4766
|
logPath,
|
|
@@ -4702,6 +4768,7 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
4702
4768
|
closeFd,
|
|
4703
4769
|
spawn: {
|
|
4704
4770
|
command: jobSpawn.command,
|
|
4771
|
+
stdinText: prompt,
|
|
4705
4772
|
// Resume mode passes `--resume <sessionId>` (reconnect to the SAME
|
|
4706
4773
|
// session) INSTEAD of `--session-id <sessionId>` (mint a new one) —
|
|
4707
4774
|
// never both, see buildClaudeSpawnArgs.
|
|
@@ -4737,7 +4804,8 @@ async function executeJob(job, runDir, defaultCwd, onPid, execCwd, resumeTarget
|
|
|
4737
4804
|
// Sync write: inside a Promise executor callback; must flush meta
|
|
4738
4805
|
// before resolve() so the spawnJob mutate() that follows sees it.
|
|
4739
4806
|
config.writeJsonSync(metaPath, { slug: job.slug, cwd, sessionId, exitCode: -1, error: errMsg, startedAt, finishedAt: Date.now(), durationMs, leakedDescendants: leaked, ...SCHEDULER_META_IDENTITY, ...setupMeta, originSessionId, contextDigestApplied });
|
|
4740
|
-
resolve({ exitCode: -1, durationMs, error: errMsg, leakedDescendants: leaked, sessionId
|
|
4807
|
+
resolve({ exitCode: -1, durationMs, error: errMsg, leakedDescendants: leaked, sessionId,
|
|
4808
|
+
...(launchFailure.isArgvTooLargeSpawnError(error?.message) ? { launchError: 'argv_too_large' } : {}) });
|
|
4741
4809
|
return;
|
|
4742
4810
|
}
|
|
4743
4811
|
|
|
@@ -5277,8 +5345,9 @@ async function spawnInvestigation(failedJob, runDir, { deadChild = null } = {})
|
|
|
5277
5345
|
closeFd,
|
|
5278
5346
|
spawn: {
|
|
5279
5347
|
command: probeSpawn.command,
|
|
5348
|
+
stdinText: prompt,
|
|
5280
5349
|
args: [
|
|
5281
|
-
'-p',
|
|
5350
|
+
'-p',
|
|
5282
5351
|
'--model', 'opus',
|
|
5283
5352
|
'--dangerously-skip-permissions',
|
|
5284
5353
|
...headlessPermissionArgs(),
|
|
@@ -6790,7 +6859,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
6790
6859
|
// says WHY it ended without anyone opening the transcript.
|
|
6791
6860
|
s.jobs[i2].terminalReason = launchFailure.deriveTerminalReason({
|
|
6792
6861
|
effectiveStatus, exitCode: res.exitCode, verifyResult, sigtermOverride, worktreeIntegrationFailure,
|
|
6793
|
-
budgetKill: !!budgetKill,
|
|
6862
|
+
budgetKill: !!budgetKill, launchError: res.launchError,
|
|
6794
6863
|
});
|
|
6795
6864
|
delete s.jobs[i2].launchFailure;
|
|
6796
6865
|
delete s.jobs[i2].heldReason;
|
|
@@ -7100,9 +7169,19 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
7100
7169
|
durationMs: res.durationMs,
|
|
7101
7170
|
transientRetries: retries,
|
|
7102
7171
|
newlyDirtyCount,
|
|
7172
|
+
launchError: res.launchError,
|
|
7103
7173
|
});
|
|
7104
7174
|
|
|
7105
|
-
if (decision.action === '
|
|
7175
|
+
if (decision.action === 'fail-launch') {
|
|
7176
|
+
console.log(`[scheduler] launch failure (${decision.launchError}) for ${job.slug} — run never started; no investigation`);
|
|
7177
|
+
await mutate((s) => {
|
|
7178
|
+
const i = s.jobs.findIndex((x) => x.slug === job.slug);
|
|
7179
|
+
if (i >= 0) {
|
|
7180
|
+
s.jobs[i].error = `launch failure (${decision.launchError}): ${res.error}; the run never started, so no investigation was spawned`;
|
|
7181
|
+
}
|
|
7182
|
+
});
|
|
7183
|
+
await broadcast({ flush: true });
|
|
7184
|
+
} else if (decision.action === 'retry') {
|
|
7106
7185
|
console.log(`[scheduler] transient failure (${decision.transientKind} dur=${res.durationMs}ms) — auto-retry ${decision.retries + 1}/${TRANSIENT_RETRY_CAP} for ${job.slug}`);
|
|
7107
7186
|
await mutate((s) => {
|
|
7108
7187
|
const i = s.jobs.findIndex((x) => x.slug === job.slug);
|
|
@@ -11243,6 +11322,8 @@ module.exports = {
|
|
|
11243
11322
|
LEFTOVER_PATHS_CAP,
|
|
11244
11323
|
capDirtyPaths,
|
|
11245
11324
|
buildForeignWipSection,
|
|
11325
|
+
limitForeignWipPaths,
|
|
11326
|
+
FOREIGN_WIP_SECTION_MAX_BYTES,
|
|
11246
11327
|
PRE_RUN_DIRTY_PATHS_CAP,
|
|
11247
11328
|
FOREIGN_WIP_DELIMITER,
|
|
11248
11329
|
FOREIGN_WIP_END_DELIMITER,
|
package/src/main/supervisor.cjs
CHANGED
|
@@ -19,6 +19,7 @@ const { ipcMain } = require('electron');
|
|
|
19
19
|
const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claude/claudeBin.cjs');
|
|
20
20
|
const { headlessPermissionArgs, ensureCliCapsProbed } = require('./lib/claude/claudeCliCaps.cjs');
|
|
21
21
|
const { withProcRole } = require('./lib/process/cleanEnv.cjs');
|
|
22
|
+
const { feedChildStdin } = require('./lib/process/childStdin.cjs');
|
|
22
23
|
const schedulerPaths = require('./lib/scheduler/schedulerPaths.cjs');
|
|
23
24
|
|
|
24
25
|
const HOME = os.homedir();
|
|
@@ -247,7 +248,7 @@ async function runProbe(claudeBin, prompt) {
|
|
|
247
248
|
try {
|
|
248
249
|
const target = claudeSpawnTarget('aux', 'supervisor', claudeBin);
|
|
249
250
|
child = spawn(target.command, [
|
|
250
|
-
'-p',
|
|
251
|
+
'-p',
|
|
251
252
|
'--model', 'claude-haiku-5-5',
|
|
252
253
|
'--no-session-persistence',
|
|
253
254
|
'--output-format', 'json',
|
|
@@ -256,7 +257,9 @@ async function runProbe(claudeBin, prompt) {
|
|
|
256
257
|
...headlessPermissionArgs(),
|
|
257
258
|
'--strict-mcp-config',
|
|
258
259
|
'--allowedTools', 'Bash',
|
|
259
|
-
], { env: withProcRole(process.env, 'aux'), stdio: ['
|
|
260
|
+
], { env: withProcRole(process.env, 'aux'), stdio: ['pipe', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
|
|
261
|
+
// Prompt on stdin, not argv: a >128 KB argv element fails spawn with E2BIG.
|
|
262
|
+
feedChildStdin(child, prompt);
|
|
260
263
|
} catch (e) {
|
|
261
264
|
console.error('[supervisor] probe spawn failed:', e?.message);
|
|
262
265
|
resolve({ verdict: 'ok', action: 'none', targetPid: null, reason: `spawn failed: ${e?.message}`, costUsd: null });
|
|
@@ -530,4 +533,5 @@ module.exports = {
|
|
|
530
533
|
applyAction,
|
|
531
534
|
registerHandlers,
|
|
532
535
|
parseProbeOutput,
|
|
536
|
+
runProbe,
|
|
533
537
|
};
|