@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 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.1030",
6
- "core_contract": "1.19.1030",
7
- "source_commit": "198e316ae502b4929018d6c592d28fd6bfa3b81d",
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:18:42.202Z",
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": "980cdb45376b2b35067d51b269d2709e1e1922dcbfb541554877cbcb9ae0c9e1",
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": "bb67419c4dd3f2311a231615a96bd462cc6806b6e3a1967ea0614e3966ef9b0e"
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": "ab4b3ba1d87734173359f3ade610dc804a339ac3a518e7e5efebee2a1ff68cd0"
8365
+ "sha256": "eb24a6596bec145cc1e850a0c17f5f9a5a05bf4b5762785c063ac9711788417e"
8366
8366
  },
8367
8367
  {
8368
8368
  "path": "package.json",
8369
8369
  "mode": "0000644",
8370
- "sha256": "ddb2d7173af124e48c454d0ba5db240e96d35541495fe7cf414246b1df433cd7"
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": "24f0be946550a89f717278a418ebb68330fad3c7cdd45d3b79e85315564c7425"
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": "3f3e44969809b3c22c9cf338af68695af6e835b861ac056d38408a4be1d202a7"
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": "439c0db441cc17b1ce9da268d30703325667f137d05f4b810786b356c9d56cfd"
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": "2f9c2b4e115b2c7411748cfe5179344bcc0e45c855c68102821966ab63ccbc21"
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.1030",
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.1030",
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.1030",
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 = process.env.OTB_CLAUDE_BIN || process.env.CLAUDE_BIN || 'claude';
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: process.platform === 'win32', // `claude` is a .cmd shim on Windows
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 = New-ScheduledTaskAction -Execute $Wrapper -WorkingDirectory $RepoRoot
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 $action -Trigger $trigger `
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.1030'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
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
+ });