Skip to content

Commit 41b40fb

Browse files
HetCreepclaude
andcommitted
fix: CoalBoard v1.3.2 — coalboardMode:off no longer silences self-update (board-audit)
Board-audit LOW (verify-triaged + 3-sub applied + reviewed, review=SHIP, 28 tests green): the conductor returned early on boardOff, suppressing the SessionStart self-update check — but coalboardMode (the board AND-gate) and updateMode (self-update) are orthogonal off-switches. Now the board's UserPromptSubmit path is skipped when off while the SessionStart self-update still fires per its own updateMode. + the companion test (had encoded the bug). Gate: build + verify + 28 tests PASS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ff01ea6 commit 41b40fb

6 files changed

Lines changed: 42 additions & 12 deletions

File tree

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "coalboard",
3-
"version": "1.3.1",
3+
"version": "1.3.2",
44
"description": "CoalBoard — a diverse-lens consensus & debate board. Its primary auto-trigger is the error-not-allowed slice (security/crypto, migrations, high-precision math); convened manually it generalizes to any hard problem worth several perspectives. On a critical task, with consent, it spawns diverse epistemic lenses in parallel — empirical (live source-grounding), formal (logic), and a show-me skeptic — a judge synthesizes on VERIFIED inputs, an independent out-of-frame solver breaks ties, and the human signs off. Guarantees bounded cost (no whack-a-mole) and zero-breakage (staging); improves correctness without claiming a reliability number. Cross-agent (any subagent platform; Claude Code adds cost-optimized tiering). Zero-dependency, offline, no API keys.",
55
"author": { "name": "HetCreep" },
66
"homepage": "https://github.com/TheColliery/CoalBoard",

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
All notable changes to CoalBoard are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow SemVer (the canonical version lives in `.claude-plugin/plugin.json`).
44

5+
## [1.3.2] — 2026-06-21
6+
7+
**PATCH** — board-audit fix (verify-triaged from the whole-Colliery nasa board); bugfix only.
8+
9+
### Fixed
10+
- **`coalboardMode:off` no longer silences self-update.** The conductor returned early on `boardOff`, which also suppressed the SessionStart self-update check — but the board AND-gate (`coalboardMode`) and self-update (`updateMode`) are ORTHOGONAL (independent off-switches). Now the board's UserPromptSubmit path is skipped when off while the SessionStart self-update still fires per its own `updateMode`. (+ the companion test, which had encoded the bug.)
11+
12+
Gate: build + verify + 28 tests PASS.
13+
514
## [1.3.1] — 2026-06-21
615

716
**PATCH**: report-context sharpen + doc sibling-consistency; no change to the board flow or config.

hooks/coalboard-conductor.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,13 @@ function updateDue(cfg) {
109109

110110
function main() {
111111
const cfg = readCfg();
112-
if (boardOff(cfg)) return;
112+
const off = boardOff(cfg);
113113
let input = {};
114114
try { const p = JSON.parse(readStdin() || '{}'); if (p && typeof p === 'object' && !Array.isArray(p)) input = p; } catch {}
115115
const event = input.hook_event_name || input.hookEventName || '';
116116

117117
if (event === 'UserPromptSubmit') {
118+
if (off) return; // the board AND-gate is gated by coalboardMode; self-update is orthogonal (SessionStart only).
118119
const reasons = detect(input.prompt || input.user_prompt || '', cfg);
119120
if (!reasons.length) return;
120121
const nonLatin = hasNonLatin(input.prompt || input.user_prompt || '')
@@ -127,11 +128,13 @@ function main() {
127128
// stays silent (Phoenix #13 zero-noise — the hook is wired to SessionStart + UserPromptSubmit;
128129
// never emit on an unexpected/unknown event).
129130
if (event !== 'SessionStart') return;
130-
let msg = "[CoalBoard] Consensus board available. On an error-not-allowed task (security/crypto, DB/financial migration, high-precision math), WITH the user's consent, convene the board: diverse lenses debate in parallel -> a judge synthesizes on VERIFIED inputs -> staged to .coalboard/proposed/ -> the human signs off. Off ~90% of the time; never touches live files until verified + approved. Judge EVERY prompt by semantic INTENT, not only the English Layer-1 keywords -- a non-English or obfuscated critical task matches no keyword seed yet still warrants the board.";
131+
// The board contract is gated by coalboardMode; self-update is ORTHOGONAL (its own off-switch
132+
// is updateMode), so it still fires when the board is off — the two keys are independent.
133+
let msg = off ? '' : "[CoalBoard] Consensus board available. On an error-not-allowed task (security/crypto, DB/financial migration, high-precision math), WITH the user's consent, convene the board: diverse lenses debate in parallel -> a judge synthesizes on VERIFIED inputs -> staged to .coalboard/proposed/ -> the human signs off. Off ~90% of the time; never touches live files until verified + approved. Judge EVERY prompt by semantic INTENT, not only the English Layer-1 keywords -- a non-English or obfuscated critical task matches no keyword seed yet still warrants the board.";
131134
if (updateDue(cfg)) {
132-
msg += ' [self-update due] Offer the /coalboard:update check (compare the latest git tag to the installed version, then offer `claude plugin update`). Consent-gated; the hook only scheduled it.';
135+
msg += (msg ? ' ' : '[CoalBoard] ') + '[self-update due] Offer the /coalboard:update check (compare the latest git tag to the installed version, then offer `claude plugin update`). Consent-gated; the hook only scheduled it.';
133136
}
134-
process.stdout.write(msg);
137+
if (msg) process.stdout.write(msg);
135138
}
136139

137140
try { main(); } catch { /* Phoenix #4: fail-silent, never crash the host */ }

plugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "coalboard",
3-
"version": "1.3.1",
3+
"version": "1.3.2",
44
"description": "CoalBoard — a diverse-lens consensus & debate board. Its primary auto-trigger is the error-not-allowed slice (security/crypto, migrations, high-precision math); convened manually it generalizes to any hard problem worth several perspectives. On a critical task, with consent, it spawns diverse epistemic lenses in parallel — empirical (live source-grounding), formal (logic), and a show-me skeptic — a judge synthesizes on VERIFIED inputs, an independent out-of-frame solver breaks ties, and the human signs off. Guarantees bounded cost (no whack-a-mole) and zero-breakage (staging); improves correctness without claiming a reliability number. Cross-agent (any subagent platform; Claude Code adds cost-optimized tiering). Zero-dependency, offline, no API keys.",
55
"author": { "name": "HetCreep" },
66
"homepage": "https://github.com/TheColliery/CoalBoard",

plugin/hooks/coalboard-conductor.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,13 @@ function updateDue(cfg) {
109109

110110
function main() {
111111
const cfg = readCfg();
112-
if (boardOff(cfg)) return;
112+
const off = boardOff(cfg);
113113
let input = {};
114114
try { const p = JSON.parse(readStdin() || '{}'); if (p && typeof p === 'object' && !Array.isArray(p)) input = p; } catch {}
115115
const event = input.hook_event_name || input.hookEventName || '';
116116

117117
if (event === 'UserPromptSubmit') {
118+
if (off) return; // the board AND-gate is gated by coalboardMode; self-update is orthogonal (SessionStart only).
118119
const reasons = detect(input.prompt || input.user_prompt || '', cfg);
119120
if (!reasons.length) return;
120121
const nonLatin = hasNonLatin(input.prompt || input.user_prompt || '')
@@ -127,11 +128,13 @@ function main() {
127128
// stays silent (Phoenix #13 zero-noise — the hook is wired to SessionStart + UserPromptSubmit;
128129
// never emit on an unexpected/unknown event).
129130
if (event !== 'SessionStart') return;
130-
let msg = "[CoalBoard] Consensus board available. On an error-not-allowed task (security/crypto, DB/financial migration, high-precision math), WITH the user's consent, convene the board: diverse lenses debate in parallel -> a judge synthesizes on VERIFIED inputs -> staged to .coalboard/proposed/ -> the human signs off. Off ~90% of the time; never touches live files until verified + approved. Judge EVERY prompt by semantic INTENT, not only the English Layer-1 keywords -- a non-English or obfuscated critical task matches no keyword seed yet still warrants the board.";
131+
// The board contract is gated by coalboardMode; self-update is ORTHOGONAL (its own off-switch
132+
// is updateMode), so it still fires when the board is off — the two keys are independent.
133+
let msg = off ? '' : "[CoalBoard] Consensus board available. On an error-not-allowed task (security/crypto, DB/financial migration, high-precision math), WITH the user's consent, convene the board: diverse lenses debate in parallel -> a judge synthesizes on VERIFIED inputs -> staged to .coalboard/proposed/ -> the human signs off. Off ~90% of the time; never touches live files until verified + approved. Judge EVERY prompt by semantic INTENT, not only the English Layer-1 keywords -- a non-English or obfuscated critical task matches no keyword seed yet still warrants the board.";
131134
if (updateDue(cfg)) {
132-
msg += ' [self-update due] Offer the /coalboard:update check (compare the latest git tag to the installed version, then offer `claude plugin update`). Consent-gated; the hook only scheduled it.';
135+
msg += (msg ? ' ' : '[CoalBoard] ') + '[self-update due] Offer the /coalboard:update check (compare the latest git tag to the installed version, then offer `claude plugin update`). Consent-gated; the hook only scheduled it.';
133136
}
134-
process.stdout.write(msg);
137+
if (msg) process.stdout.write(msg);
135138
}
136139

137140
try { main(); } catch { /* Phoenix #4: fail-silent, never crash the host */ }

scripts/lib/conductor.test.mjs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,28 @@ test('emoji in an English prompt -> CRITICAL signal but NO non-English nudge (em
9090
} finally { fs.rmSync(tmp, { recursive: true, force: true }); }
9191
});
9292

93-
test('coalboardMode:off -> fully silent on every event', () => {
93+
test('coalboardMode:off -> board contract + AND-gate silent, but self-update is orthogonal', () => {
9494
const tmp = mk();
9595
try {
9696
writeCfg(tmp, { coalboardMode: 'off' });
97+
// SessionStart: the board contract is suppressed, but the self-update is due (fresh sandbox,
98+
// updateMode defaults to ask) -> only the self-update directive fires, NOT the board contract.
9799
const r1 = run({ hook_event_name: 'SessionStart' }, tmp, tmp);
98100
const r2 = run({ hook_event_name: 'UserPromptSubmit', prompt: 'fix the auth crypto bug' }, tmp, tmp);
99-
assert.equal(r1.status, 0); assert.equal(r1.stdout, '');
101+
assert.equal(r1.status, 0);
102+
assert.match(r1.stdout, /self-update due/, 'self-update is orthogonal to the board and still fires when off');
103+
assert.doesNotMatch(r1.stdout, /Consensus board available/, 'the board contract is suppressed when coalboardMode:off');
104+
assert.equal(r2.status, 0); assert.equal(r2.stdout, '', 'the UserPromptSubmit AND-gate stays silent when the board is off');
105+
} finally { fs.rmSync(tmp, { recursive: true, force: true }); }
106+
});
107+
108+
test('coalboardMode:off + updateMode:off -> fully silent on every event', () => {
109+
const tmp = mk();
110+
try {
111+
writeCfg(tmp, { coalboardMode: 'off', updateMode: 'off' });
112+
const r1 = run({ hook_event_name: 'SessionStart' }, tmp, tmp);
113+
const r2 = run({ hook_event_name: 'UserPromptSubmit', prompt: 'fix the auth crypto bug' }, tmp, tmp);
114+
assert.equal(r1.status, 0); assert.equal(r1.stdout, '', 'board off + update off -> SessionStart fully silent');
100115
assert.equal(r2.status, 0); assert.equal(r2.stdout, '');
101116
} finally { fs.rmSync(tmp, { recursive: true, force: true }); }
102117
});

0 commit comments

Comments
 (0)