@bongos/core 1.19.1030 → 1.19.1031
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/.bongos-core.json +12 -12
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/autobongos-run.js +51 -3
- package/scripts/gds/autobongos-service.ps1 +7 -2
- package/src/module-api.js +1 -1
- package/tests/autobongos_loop.mjs +91 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.1031",
|
|
6
|
+
"core_contract": "1.19.1031",
|
|
7
|
+
"source_commit": "04f5126869533ad9e3a29fa072dd288ba6e63f3b",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-25T21:
|
|
9
|
+
"built_at": "2026-09-25T21:32:54.328Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 529,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 3014,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "05efd2e30efe440d0892c97889cdea81127c2544ef93366c2b054750750fceb3",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2757,7 +2757,7 @@
|
|
|
2757
2757
|
{
|
|
2758
2758
|
"path": "docs/module-api-changelog.md",
|
|
2759
2759
|
"mode": "0000644",
|
|
2760
|
-
"sha256": "
|
|
2760
|
+
"sha256": "6f37eb2ce46310219bfc4f0c4820e67f6afda57cfe76cb2a0e8cf75d3bd6577d"
|
|
2761
2761
|
},
|
|
2762
2762
|
{
|
|
2763
2763
|
"path": "docs/modules-contract.md",
|
|
@@ -8362,12 +8362,12 @@
|
|
|
8362
8362
|
{
|
|
8363
8363
|
"path": "package-lock.json",
|
|
8364
8364
|
"mode": "0000644",
|
|
8365
|
-
"sha256": "
|
|
8365
|
+
"sha256": "eb24a6596bec145cc1e850a0c17f5f9a5a05bf4b5762785c063ac9711788417e"
|
|
8366
8366
|
},
|
|
8367
8367
|
{
|
|
8368
8368
|
"path": "package.json",
|
|
8369
8369
|
"mode": "0000644",
|
|
8370
|
-
"sha256": "
|
|
8370
|
+
"sha256": "84bad55379e5b9c61129a8d4cae7b074f22ba99984543dd1ac990688ac73efcb"
|
|
8371
8371
|
},
|
|
8372
8372
|
{
|
|
8373
8373
|
"path": "public-docs/index.html",
|
|
@@ -8542,7 +8542,7 @@
|
|
|
8542
8542
|
{
|
|
8543
8543
|
"path": "scripts/gds/autobongos-run.js",
|
|
8544
8544
|
"mode": "0000644",
|
|
8545
|
-
"sha256": "
|
|
8545
|
+
"sha256": "9fe8be8bc38039f700b6aab8b52508c162252513cc6b5b87aacb7dc48648937a"
|
|
8546
8546
|
},
|
|
8547
8547
|
{
|
|
8548
8548
|
"path": "scripts/gds/autobongos-service.cmd",
|
|
@@ -8552,7 +8552,7 @@
|
|
|
8552
8552
|
{
|
|
8553
8553
|
"path": "scripts/gds/autobongos-service.ps1",
|
|
8554
8554
|
"mode": "0000644",
|
|
8555
|
-
"sha256": "
|
|
8555
|
+
"sha256": "ed95f836655535827c14a32caf197fee7c238e15d8e8bf7a9e987b78ab266887"
|
|
8556
8556
|
},
|
|
8557
8557
|
{
|
|
8558
8558
|
"path": "scripts/gds/autobongos-verify.js",
|
|
@@ -10412,7 +10412,7 @@
|
|
|
10412
10412
|
{
|
|
10413
10413
|
"path": "src/module-api.js",
|
|
10414
10414
|
"mode": "0000644",
|
|
10415
|
-
"sha256": "
|
|
10415
|
+
"sha256": "320ce97feaea53ab38c025dbba20001ef89643b0519151e010c7456e0dd727eb"
|
|
10416
10416
|
},
|
|
10417
10417
|
{
|
|
10418
10418
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10847,7 +10847,7 @@
|
|
|
10847
10847
|
{
|
|
10848
10848
|
"path": "tests/autobongos_loop.mjs",
|
|
10849
10849
|
"mode": "0000644",
|
|
10850
|
-
"sha256": "
|
|
10850
|
+
"sha256": "8ba5a6b492ae5afabb13f9e44d2f82cb873a094a659425554f0d105bd979aff4"
|
|
10851
10851
|
},
|
|
10852
10852
|
{
|
|
10853
10853
|
"path": "tests/autobongos_verify.mjs",
|
|
@@ -2539,5 +2539,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2539
2539
|
landed since 1.19.1028 with no explicit bump. run 36189693139. (task 1002620)
|
|
2540
2540
|
1.19.1030 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2541
2541
|
landed since 1.19.1029 with no explicit bump. run 36190779629. (task 1002620)
|
|
2542
|
+
1.19.1031 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2543
|
+
landed since 1.19.1030 with no explicit bump. run 36192093201. (task 1002620)
|
|
2542
2544
|
---------------------------------------------------------------------------
|
|
2543
2545
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1031",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.1031",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1031",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -162,6 +162,20 @@ const WORKER_ENV_ALLOW = [
|
|
|
162
162
|
'BONGOS_API_BASE', 'CLOUDBONGOS_API_BASE',
|
|
163
163
|
'OTB_CLAUDE_BIN', 'CLAUDE_BIN',
|
|
164
164
|
'XDG_CONFIG_HOME',
|
|
165
|
+
// THE WORKER'S OWN CREDENTIAL, and it has to be here (task 1003907, found by
|
|
166
|
+
// running it). On a desktop, the claude CLI authenticates from the credential
|
|
167
|
+
// store under USERPROFILE/HOME, which are already allowed - so this list looked
|
|
168
|
+
// complete. On a HEADLESS host there is no interactive login to have written
|
|
169
|
+
// one, and setup.js's own preflight says to set CLAUDE_CODE_OAUTH_TOKEN there
|
|
170
|
+
// instead. Stripping it meant every worker on the machine this was built for
|
|
171
|
+
// started unauthenticated, exited in ~2s, and the run recorded only "worker
|
|
172
|
+
// output was not JSON".
|
|
173
|
+
//
|
|
174
|
+
// This is not the leak the list exists to prevent. The worker IS a Claude
|
|
175
|
+
// session; its own auth is the one credential it cannot do its job without.
|
|
176
|
+
// Everything the comment above refuses - DB URLs, git and npm tokens - stays
|
|
177
|
+
// refused.
|
|
178
|
+
'CLAUDE_CODE_OAUTH_TOKEN', 'ANTHROPIC_API_KEY',
|
|
165
179
|
];
|
|
166
180
|
|
|
167
181
|
function workerEnv(source = process.env, extra = {}) {
|
|
@@ -175,12 +189,46 @@ function workerEnv(source = process.env, extra = {}) {
|
|
|
175
189
|
|
|
176
190
|
const MAX_WORKER_OUTPUT = 8 * 1024 * 1024; // a misbehaving worker must not grow memory until the timeout
|
|
177
191
|
|
|
192
|
+
// resolveWorkerBin — WHICH `claude`, and whether it needs a shell.
|
|
193
|
+
//
|
|
194
|
+
// THE BUG THIS EXISTS FOR (task 1003907, found by running it on the real Windows
|
|
195
|
+
// host). On Windows `claude` on PATH is a .cmd shim, and Node has refused to
|
|
196
|
+
// spawn .cmd/.bat without a shell since CVE-2024-27980 — so this used to pass
|
|
197
|
+
// `shell: process.platform === 'win32'`. But `shell: true` does not pass argv
|
|
198
|
+
// through: it CONCATENATES it into one command line that cmd.exe re-parses,
|
|
199
|
+
// which destroys every embedded quote. `--json-schema` is a JSON blob made
|
|
200
|
+
// almost entirely of quotes, so claude received a mangled argument and exited in
|
|
201
|
+
// ~2s with `--json-schema is not valid JSON`. Every worker. Every run. Forever —
|
|
202
|
+
// and the cadence classes that as `quiet`, so it never trips the failure ladder.
|
|
203
|
+
//
|
|
204
|
+
// The npm package ships a REAL claude.exe beside the shim, so prefer it: a true
|
|
205
|
+
// executable spawns with shell:false and exact argv, and nothing re-parses.
|
|
206
|
+
// Falling back to the old behaviour is deliberate — a host with a differently
|
|
207
|
+
// laid-out install should still run, and a corrupted schema surfaces as a failed
|
|
208
|
+
// worker rather than as a refusal to start at all.
|
|
209
|
+
function resolveWorkerBin(env = process.env, platform = process.platform) {
|
|
210
|
+
const override = env.OTB_CLAUDE_BIN || env.CLAUDE_BIN;
|
|
211
|
+
if (override) return { bin: override, shell: platform === 'win32' && /\.(cmd|bat)$/i.test(override) };
|
|
212
|
+
if (platform !== 'win32') return { bin: 'claude', shell: false };
|
|
213
|
+
for (const dir of String(env.PATH || env.Path || '').split(path.delimiter)) {
|
|
214
|
+
if (!dir) continue;
|
|
215
|
+
const candidates = [
|
|
216
|
+
path.join(dir, 'node_modules', '@anthropic-ai', 'claude-code', 'bin', 'claude.exe'),
|
|
217
|
+
path.join(dir, 'claude.exe'),
|
|
218
|
+
];
|
|
219
|
+
for (const c of candidates) {
|
|
220
|
+
try { if (fs.existsSync(c)) return { bin: c, shell: false }; } catch (_) {}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { bin: 'claude', shell: true };
|
|
224
|
+
}
|
|
225
|
+
|
|
178
226
|
// spawnWorker — one headless session, bounded by WALL CLOCK, in the task's own
|
|
179
227
|
// working tree.
|
|
180
228
|
async function spawnWorker({ task, model, timeoutMs, cwd, deps = {} }) {
|
|
181
229
|
const spawnFn = deps.spawn || spawn;
|
|
182
230
|
const sessionId = deps.sessionId || crypto.randomUUID(); // pinned, so the session can be resumed
|
|
183
|
-
const bin =
|
|
231
|
+
const { bin, shell: workerShell } = deps.resolveBin ? deps.resolveBin() : resolveWorkerBin();
|
|
184
232
|
const isRoot = typeof process.getuid === 'function' && process.getuid() === 0;
|
|
185
233
|
const args = buildWorkerArgs({ sessionId, model, isRoot });
|
|
186
234
|
const prompt = buildWorkerPrompt({ task });
|
|
@@ -194,7 +242,7 @@ async function spawnWorker({ task, model, timeoutMs, cwd, deps = {} }) {
|
|
|
194
242
|
// `claude -p` with an open inherited stdin waits seconds for input that
|
|
195
243
|
// never comes, on every single spawn.
|
|
196
244
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
197
|
-
shell:
|
|
245
|
+
shell: workerShell,
|
|
198
246
|
windowsHide: true,
|
|
199
247
|
env: workerEnv(),
|
|
200
248
|
});
|
|
@@ -749,7 +797,7 @@ if (require.main === module) {
|
|
|
749
797
|
}
|
|
750
798
|
|
|
751
799
|
module.exports = {
|
|
752
|
-
readArgv, pickTask, iteration, record, logPath, spawnWorker, workerEnv, WORKER_ENV_ALLOW,
|
|
800
|
+
readArgv, pickTask, iteration, record, logPath, spawnWorker, workerEnv, WORKER_ENV_ALLOW, resolveWorkerBin,
|
|
753
801
|
worktreeName, readFence, reconcileLeftoverClaims, pollSignals, waitOrJump, forever, CLAIM_PREFIX,
|
|
754
802
|
heartbeatPath, writeHeartbeat, readHeartbeat, pidAlive, anotherRunnerIsAlive, HEARTBEAT_STALE_MS,
|
|
755
803
|
isRunnerTree, RUNNER_TREE_RE, postHeartbeat, RUNNER_STARTED_AT,
|
|
@@ -89,7 +89,12 @@ switch ($Action) {
|
|
|
89
89
|
Assert-Prereqs
|
|
90
90
|
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
|
91
91
|
|
|
92
|
-
$action
|
|
92
|
+
# NOT $action: PowerShell variable names are CASE-INSENSITIVE, so $action IS the
|
|
93
|
+
# $Action parameter above, and its [ValidateSet]/[string] attributes stay attached
|
|
94
|
+
# to the variable. Assigning a task-action object to it throws "the value
|
|
95
|
+
# MSFT_TaskExecAction is not a valid value for the Action variable" and install
|
|
96
|
+
# fails every time, on every machine. Same trap as the `run` shadow in task 1003908.
|
|
97
|
+
$taskAction = New-ScheduledTaskAction -Execute $Wrapper -WorkingDirectory $RepoRoot
|
|
93
98
|
# AtStartup covers the cold reboot the done-when actually names. AtLogOn is
|
|
94
99
|
# NOT used: it would wait for someone to sign in, which is the one thing that
|
|
95
100
|
# must not be required.
|
|
@@ -107,7 +112,7 @@ switch ($Action) {
|
|
|
107
112
|
# MultipleInstances IgnoreNew = the scheduler will not start a second copy
|
|
108
113
|
# over a live one; the heartbeat file is the second guard, inside the runner.
|
|
109
114
|
|
|
110
|
-
Register-ScheduledTask -TaskName $TaskName -Action $
|
|
115
|
+
Register-ScheduledTask -TaskName $TaskName -Action $taskAction -Trigger $trigger `
|
|
111
116
|
-Principal $principal -Settings $settings -Force | Out-Null
|
|
112
117
|
|
|
113
118
|
Write-Host "installed '$TaskName' -- starts at boot, restarts on crash, logs to $LogFile"
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.1031'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -563,3 +563,94 @@ test('the module-level exec helper is reachable from inside iteration()', async
|
|
|
563
563
|
assert.equal(picked, true, 'iteration must reach the picker without touching an un-injected binding');
|
|
564
564
|
assert.equal(row.event, 'nothing_claimable');
|
|
565
565
|
});
|
|
566
|
+
|
|
567
|
+
// --- resolveWorkerBin: the Windows spawn that corrupted every worker ---------
|
|
568
|
+
//
|
|
569
|
+
// Found by task 1003907 running the thing rather than testing it. `shell: true`
|
|
570
|
+
// does not pass argv through on Windows: it CONCATENATES it into one command
|
|
571
|
+
// line that cmd.exe re-parses, destroying the quotes in --json-schema. claude
|
|
572
|
+
// then exited in ~2s with "--json-schema is not valid JSON" on EVERY worker, and
|
|
573
|
+
// cadence.js classes that outcome as `quiet`, which neither trips nor forgives
|
|
574
|
+
// the failure count — so a totally broken runner never escalated and looked
|
|
575
|
+
// healthy in the hall. The invariant: on win32, never ask for a shell when a
|
|
576
|
+
// real executable is available.
|
|
577
|
+
//
|
|
578
|
+
// Paths here use forward slashes deliberately. The resolver only string-matches
|
|
579
|
+
// the extension on an override, and backslash-heavy literals are their own
|
|
580
|
+
// source of bugs in a test that exists because of quoting.
|
|
581
|
+
|
|
582
|
+
test('resolveWorkerBin: a .cmd override still gets its shell', () => {
|
|
583
|
+
const got = runner.resolveWorkerBin({ CLAUDE_BIN: 'C:/x/claude.cmd' }, 'win32');
|
|
584
|
+
assert.equal(got.bin, 'C:/x/claude.cmd');
|
|
585
|
+
assert.equal(got.shell, true, 'Node cannot spawn a .cmd without a shell (CVE-2024-27980)');
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
test('resolveWorkerBin: a real .exe override is never re-parsed by cmd.exe', () => {
|
|
589
|
+
const got = runner.resolveWorkerBin({ OTB_CLAUDE_BIN: 'C:/x/claude.exe' }, 'win32');
|
|
590
|
+
assert.equal(got.shell, false);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test('resolveWorkerBin: win32 with nothing on PATH falls back rather than refusing', () => {
|
|
594
|
+
const got = runner.resolveWorkerBin({ PATH: 'C:/nowhere-at-all' }, 'win32');
|
|
595
|
+
assert.deepEqual(got, { bin: 'claude', shell: true },
|
|
596
|
+
'a differently-laid-out install must still start; a mangled schema surfaces as a failed worker');
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test('resolveWorkerBin: non-windows never asks for a shell', () => {
|
|
600
|
+
assert.deepEqual(runner.resolveWorkerBin({ PATH: '/usr/bin' }, 'linux'), { bin: 'claude', shell: false });
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test('the schema argument is the one that cannot survive concatenation', () => {
|
|
604
|
+
const args = buildWorkerArgs({ sessionId: 'abc' });
|
|
605
|
+
const schema = args[args.indexOf('--json-schema') + 1];
|
|
606
|
+
assert.ok(schema.includes('"'), 'the regression only bites because the schema carries quotes');
|
|
607
|
+
assert.ok(schema.length > 50, 'and because it must arrive as one intact argv slot');
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// The test that would actually have caught the shipped bug: the resolver's
|
|
611
|
+
// answer has to REACH the spawn options. Testing resolveWorkerBin alone leaves
|
|
612
|
+
// `shell: process.platform === 'win32'` free to reappear at the call site.
|
|
613
|
+
test('spawnWorker passes the resolver’s shell verdict straight to spawn', async () => {
|
|
614
|
+
const { EventEmitter } = require('node:events');
|
|
615
|
+
let opts = null; let binSeen = null; let argvSeen = null;
|
|
616
|
+
const fakeSpawn = (bin, args, o) => {
|
|
617
|
+
binSeen = bin; argvSeen = args; opts = o;
|
|
618
|
+
const child = new EventEmitter();
|
|
619
|
+
child.stdout = new EventEmitter();
|
|
620
|
+
child.stderr = new EventEmitter();
|
|
621
|
+
child.stdin = { end: () => setImmediate(() => child.emit('close', 0)) };
|
|
622
|
+
child.kill = () => {};
|
|
623
|
+
return child;
|
|
624
|
+
};
|
|
625
|
+
await runner.spawnWorker({
|
|
626
|
+
task: { id: 7, title: 't', description: 'd' },
|
|
627
|
+
timeoutMs: 1000,
|
|
628
|
+
cwd: '.',
|
|
629
|
+
deps: { spawn: fakeSpawn, resolveBin: () => ({ bin: 'C:/x/claude.exe', shell: false }) },
|
|
630
|
+
});
|
|
631
|
+
assert.equal(opts.shell, false, 'a resolved .exe must spawn WITHOUT a shell — this is the whole fix');
|
|
632
|
+
assert.equal(binSeen, 'C:/x/claude.exe');
|
|
633
|
+
const schema = argvSeen[argvSeen.indexOf('--json-schema') + 1];
|
|
634
|
+
assert.equal(schema, JSON.stringify(WORKER_VERDICT_SCHEMA), 'the schema must arrive as one intact argv slot');
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// The worker's own credential must survive the env allowlist (task 1003907).
|
|
638
|
+
// Stripping it meant every worker on a headless host started unauthenticated and
|
|
639
|
+
// exited in ~2s with no usable output — while the supervisor recorded only
|
|
640
|
+
// "worker output was not JSON" and the cadence called that quiet.
|
|
641
|
+
test('workerEnv passes Claude auth through, and still refuses the rest', () => {
|
|
642
|
+
const src = {
|
|
643
|
+
PATH: '/usr/bin',
|
|
644
|
+
CLAUDE_CODE_OAUTH_TOKEN: 'tok',
|
|
645
|
+
ANTHROPIC_API_KEY: 'key',
|
|
646
|
+
DATABASE_URL: 'postgres://nope',
|
|
647
|
+
GITHUB_TOKEN: 'ghp_nope',
|
|
648
|
+
NPM_TOKEN: 'npm_nope',
|
|
649
|
+
};
|
|
650
|
+
const env = runner.workerEnv(src);
|
|
651
|
+
assert.equal(env.CLAUDE_CODE_OAUTH_TOKEN, 'tok', 'a worker that cannot authenticate cannot work');
|
|
652
|
+
assert.equal(env.ANTHROPIC_API_KEY, 'key');
|
|
653
|
+
for (const leak of ['DATABASE_URL', 'GITHUB_TOKEN', 'NPM_TOKEN']) {
|
|
654
|
+
assert.equal(env[leak], undefined, `${leak} must never reach a bypassPermissions worker`);
|
|
655
|
+
}
|
|
656
|
+
});
|