From 5ac6c87053385a2f61b3eaeff9d7c70730000625 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Sat, 20 Jun 2026 10:12:52 +0200 Subject: [PATCH] =?UTF-8?q?fix(hkv):=20remove=20post-session=20=E2=80=94?= =?UTF-8?q?=20it=20is=20a=20runner=20hook,=20not=20a=20settings=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified 2026-06-20 against hooks.md + the 2.1.169 changelog: the `post-session` hook in that changelog is a SELF-HOSTED-RUNNER workspace-lifecycle hook (runs after the session, before the workspace is deleted), NOT a settings.json hook event. It is absent from hooks.md's 30-event list (all PascalCase), so config-audit was wrongly treating a bogus `post-session` settings hook as valid — it now flags it. Restructured the event test suite accordingly and added a negative test. Resolved U1/U2 in the v5.5 plan (U1 refuted → removed; U2 `outputStyles` plugin.json key confirmed → PLH unchanged). Suite stays 954, self-audit A/A. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8 --- docs/v5.5-steering-model-plan.md | 8 ++-- scanners/hook-validator.mjs | 8 ++-- tests/scanners/hook-validator.test.mjs | 63 ++++++++++++++++++++------ 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/docs/v5.5-steering-model-plan.md b/docs/v5.5-steering-model-plan.md index 0112cc1..d73e168 100644 --- a/docs/v5.5-steering-model-plan.md +++ b/docs/v5.5-steering-model-plan.md @@ -48,8 +48,8 @@ docs; `REFUTED` = docs contradict; `UNVERIFIED` = not found in primary docs (do | V17 | Required agent frontmatter = **only `name` + `description`** | CONFIRMED | `sub-agents.md` | | V18 | Hooks: **~30 events** (not "five"); hook scripts run outside context, but injected `additionalContext` IS saved to transcript (subject to compaction) | CONFIRMED | `hooks.md` | | V19 | An official **mechanism-fit comparison table** exists (output style vs CLAUDE.md vs `--append-system-prompt` vs agents vs skills) | CONFIRMED | `output-styles.md` | -| U1 | hook event `post-session` (kebab) | **UNVERIFIED** — config-audit's `VALID_EVENTS` lists it (2.1.169 comment) but not found in current `hooks.md`; a test depends on it. Follow-up check before v5.5. | — | -| U2 | A plugin.json `outputStyles` path-override key (PLH `SHADOWING_PATH_FIELDS`) | **CONFIRM** — pinned to path-behavior docs in PLH; one research aside disputed it. Re-confirm against `plugin-reference` before touching. See `[[plugin-json-path-behavior]]`. | — | +| U1 | hook event `post-session` (kebab) | **RESOLVED → REFUTED** (2026-06-20). The 2.1.169 changelog `post-session` is a **self-hosted-runner** workspace-lifecycle hook, NOT a settings.json hook event; absent from `hooks.md` (all 30 events PascalCase). **Removed** from HKV `VALID_EVENTS` in v5.4.1. | `hooks.md`, changelog 2.1.169 | +| U2 | A plugin.json `outputStyles` path-override key (PLH `SHADOWING_PATH_FIELDS`) | **RESOLVED → CONFIRMED** (2026-06-20). `outputStyles` (camelCase) is a documented plugin.json key in the **replaces** category (default `output-styles/` ignored when set). PLH is correct — no change. See `[[plugin-json-path-behavior]]`. | `plugins-reference.md` | --- @@ -228,8 +228,8 @@ for separating them from the low-risk additive batch.) (and D if it touches the snapshot project). - [ ] Scanner-count lore updated everywhere if C lands (README badge, CLAUDE.md inventory, `docs/scanner-internals.md`, the "count stays 13" notes). -- [ ] U1 (`post-session`) and U2 (plugin `outputStyles` key) resolved before the features that - depend on them. +- [x] U1 (`post-session`) and U2 (plugin `outputStyles` key) resolved (2026-06-20): U1 refuted + → removed from HKV in v5.4.1; U2 confirmed → PLH unchanged. ## Open decisions for the operator diff --git a/scanners/hook-validator.mjs b/scanners/hook-validator.mjs index 49dba8d..f3811a5 100644 --- a/scanners/hook-validator.mjs +++ b/scanners/hook-validator.mjs @@ -28,10 +28,10 @@ const VALID_EVENTS = new Set([ 'PreCompact', 'PostCompact', 'Elicitation', 'ElicitationResult', 'SessionEnd', 'MessageDisplay', - // NOTE: 'post-session' (kebab) is UNVERIFIED against current docs — kept - // pending confirmation (an existing test depends on it; hook events are - // otherwise PascalCase, so this is flagged for a follow-up check). - 'post-session', + // 'post-session' deliberately EXCLUDED: the 2.1.169 changelog `post-session` + // is a self-hosted-runner workspace-lifecycle hook, NOT a settings.json hook + // event (absent from hooks.md; all settings.json events are PascalCase). + // Verified 2026-06-20. ]); /** Valid hook handler types */ diff --git a/tests/scanners/hook-validator.test.mjs b/tests/scanners/hook-validator.test.mjs index 871a36f..125cc87 100644 --- a/tests/scanners/hook-validator.test.mjs +++ b/tests/scanners/hook-validator.test.mjs @@ -101,13 +101,13 @@ describe('HKV scanner — verbose hook output (v5 M5)', () => { }); }); -describe('HKV scanner — CC 2.1.152/2.1.169 hook events (Batch 1 false-positive fix)', () => { +describe('HKV scanner — CC 2.1.152 MessageDisplay event (Batch 1 false-positive fix)', () => { // The pre-write path-guard blocks committing settings.json/hooks.json, so // this suite materializes a hermetic temp fixture at runtime. let tmpRoot; let result; - const NEW_EVENTS = ['MessageDisplay', 'post-session']; + const VALID_NEW = ['MessageDisplay']; beforeEach(async () => { resetCounter(); @@ -118,6 +118,50 @@ describe('HKV scanner — CC 2.1.152/2.1.169 hook events (Batch 1 false-positive // 'echo …' commands skip the script-existence check (extractScriptPath // only resolves bash/node/sh), isolating the event-name validation. MessageDisplay: [{ hooks: [{ type: 'command', command: 'echo display' }] }], + }, + }; + await writeFile( + join(tmpRoot, '.claude', 'settings.json'), + JSON.stringify(settings, null, 2) + '\n', + 'utf8', + ); + const discovery = await discoverConfigFiles(tmpRoot); + result = await scan(tmpRoot, discovery); + }); + + afterEach(async () => { + if (tmpRoot) await rm(tmpRoot, { recursive: true, force: true }); + }); + + for (const event of VALID_NEW) { + it(`does NOT flag "${event}" as an unknown hook event`, () => { + const unknown = result.findings.find(f => + f.title === 'Unknown hook event' && f.evidence === event); + assert.equal(unknown, undefined, `${event} should be in VALID_EVENTS`); + }); + } + + it('produces zero findings for a valid MessageDisplay config', () => { + assert.equal(result.findings.length, 0, + `expected clean scan; got: ${result.findings.map(f => `${f.title}:${f.evidence || ''}`).join(' | ')}`); + }); +}); + +describe('HKV scanner — post-session is NOT a settings.json hook event (Verifiseringsplikt)', () => { + // Verified 2026-06-20 against hooks.md + the 2.1.169 changelog: the + // `post-session` hook in that changelog is a SELF-HOSTED-RUNNER + // workspace-lifecycle hook, NOT a settings.json hook event. It is absent + // from hooks.md's (all-PascalCase) event list, so a settings.json hook + // keyed on it never fires and MUST be flagged. + let tmpRoot; + let result; + + beforeEach(async () => { + resetCounter(); + tmpRoot = await mkdtemp(join(tmpdir(), 'ca-hkv-postsession-')); + await mkdir(join(tmpRoot, '.claude'), { recursive: true }); + const settings = { + hooks: { 'post-session': [{ hooks: [{ type: 'command', command: 'echo bye' }] }], }, }; @@ -134,17 +178,10 @@ describe('HKV scanner — CC 2.1.152/2.1.169 hook events (Batch 1 false-positive if (tmpRoot) await rm(tmpRoot, { recursive: true, force: true }); }); - for (const event of NEW_EVENTS) { - it(`does NOT flag "${event}" as an unknown hook event`, () => { - const unknown = result.findings.find(f => - f.title === 'Unknown hook event' && f.evidence === event); - assert.equal(unknown, undefined, `${event} should be in VALID_EVENTS`); - }); - } - - it('produces zero findings for a valid MessageDisplay + post-session config', () => { - assert.equal(result.findings.length, 0, - `expected clean scan; got: ${result.findings.map(f => `${f.title}:${f.evidence || ''}`).join(' | ')}`); + it('flags "post-session" as an unknown hook event', () => { + const f = result.findings.find(x => + x.title === 'Unknown hook event' && x.evidence === 'post-session'); + assert.ok(f, 'post-session must be flagged as an unknown settings.json hook event'); }); });