From c04848ce3fbda43ab271fe8791f57b9ab075f5f2 Mon Sep 17 00:00:00 2001 From: Aymeric Rabot Date: Mon, 7 Sep 2026 13:53:17 -0400 Subject: [PATCH 1/2] Fix Codex hook output envelope --- .claude-plugin/marketplace.json | 4 ++-- install.md | 2 +- plugins/claude-code/.claude-plugin/plugin.json | 2 +- plugins/claude-code/blaze-client.mjs | 7 ++++--- plugins/claude-code/skills/blaze/SKILL.md | 2 +- plugins/claude-code/skills/blaze/blaze-client.mjs | 7 ++++--- plugins/client/blaze-client.mjs | 7 ++++--- plugins/client/blaze-client.test.mjs | 12 +++++++++++- release.json | 6 +++--- skill.md | 2 +- 10 files changed, 32 insertions(+), 19 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 784a897..0b51919 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,14 +7,14 @@ }, "metadata": { "description": "Verified Solution Cards retrieved from an agent-authored conceptual problem statement.", - "version": "0.4.2" + "version": "0.4.3" }, "plugins": [ { "name": "blaze", "source": "./plugins/claude-code", "description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.", - "version": "0.4.2", + "version": "0.4.3", "keywords": [ "memory", "retrieval", diff --git a/install.md b/install.md index 284a579..a2f54be 100644 --- a/install.md +++ b/install.md @@ -1,4 +1,4 @@ -# Install Blaze 0.4.2 +# Install Blaze 0.4.3 Blaze shares verified coding lessons through an explicitly invoked client. Installing it does not authorize uploading prompts, source or transcripts. diff --git a/plugins/claude-code/.claude-plugin/plugin.json b/plugins/claude-code/.claude-plugin/plugin.json index ed7ecba..570e978 100644 --- a/plugins/claude-code/.claude-plugin/plugin.json +++ b/plugins/claude-code/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://anthropic.com/claude-code/plugin.schema.json", "name": "blaze", "displayName": "Blaze", - "version": "0.4.2", + "version": "0.4.3", "description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.", "author": { "name": "Blaze" diff --git a/plugins/claude-code/blaze-client.mjs b/plugins/claude-code/blaze-client.mjs index fcd1ea1..f451235 100644 --- a/plugins/claude-code/blaze-client.mjs +++ b/plugins/claude-code/blaze-client.mjs @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.4.2"; +export const CLIENT_VERSION = "0.4.3"; export const CLIENT_CONTRACT = 1; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"]; @@ -465,14 +465,15 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre return { deleted: true }; }, async hook(body) { - const event = String(body.hook_event_name ?? body.event ?? "UserPromptSubmit"); + const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; + if (event !== "UserPromptSubmit" && !(typeof event === "string" && ENDS.has(event.toLowerCase()))) return {}; if (ENDS.has(event.toLowerCase())) return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, "Inspect the exact query before sending it. Local validation is a guardrail, not proof that text is safe to disclose.", ].join("\n"); - return { additionalContext, hookSpecificOutput: { hookEventName: event, additionalContext } }; + return { hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext } }; }, async lookup(body) { return retrieve(body, "UserPromptSubmit"); }, async card(decisionId, cardId) { diff --git a/plugins/claude-code/skills/blaze/SKILL.md b/plugins/claude-code/skills/blaze/SKILL.md index 93eb205..e4d3d2c 100644 --- a/plugins/claude-code/skills/blaze/SKILL.md +++ b/plugins/claude-code/skills/blaze/SKILL.md @@ -3,7 +3,7 @@ name: blaze description: Reuse and improve verified coding lessons across agents. Use for a nontrivial debugging or implementation problem where an earlier solution could help, when a Blaze offer or receipt appears, or when the user asks to install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition. compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local reminder hooks need no network access. No model provider credentials are needed. metadata: - version: "0.4.2" + version: "0.4.3" --- # Blaze diff --git a/plugins/claude-code/skills/blaze/blaze-client.mjs b/plugins/claude-code/skills/blaze/blaze-client.mjs index fcd1ea1..f451235 100644 --- a/plugins/claude-code/skills/blaze/blaze-client.mjs +++ b/plugins/claude-code/skills/blaze/blaze-client.mjs @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.4.2"; +export const CLIENT_VERSION = "0.4.3"; export const CLIENT_CONTRACT = 1; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"]; @@ -465,14 +465,15 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre return { deleted: true }; }, async hook(body) { - const event = String(body.hook_event_name ?? body.event ?? "UserPromptSubmit"); + const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; + if (event !== "UserPromptSubmit" && !(typeof event === "string" && ENDS.has(event.toLowerCase()))) return {}; if (ENDS.has(event.toLowerCase())) return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, "Inspect the exact query before sending it. Local validation is a guardrail, not proof that text is safe to disclose.", ].join("\n"); - return { additionalContext, hookSpecificOutput: { hookEventName: event, additionalContext } }; + return { hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext } }; }, async lookup(body) { return retrieve(body, "UserPromptSubmit"); }, async card(decisionId, cardId) { diff --git a/plugins/client/blaze-client.mjs b/plugins/client/blaze-client.mjs index fcd1ea1..f451235 100644 --- a/plugins/client/blaze-client.mjs +++ b/plugins/client/blaze-client.mjs @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.4.2"; +export const CLIENT_VERSION = "0.4.3"; export const CLIENT_CONTRACT = 1; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"]; @@ -465,14 +465,15 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre return { deleted: true }; }, async hook(body) { - const event = String(body.hook_event_name ?? body.event ?? "UserPromptSubmit"); + const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; + if (event !== "UserPromptSubmit" && !(typeof event === "string" && ENDS.has(event.toLowerCase()))) return {}; if (ENDS.has(event.toLowerCase())) return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, "Inspect the exact query before sending it. Local validation is a guardrail, not proof that text is safe to disclose.", ].join("\n"); - return { additionalContext, hookSpecificOutput: { hookEventName: event, additionalContext } }; + return { hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext } }; }, async lookup(body) { return retrieve(body, "UserPromptSubmit"); }, async card(decisionId, cardId) { diff --git a/plugins/client/blaze-client.test.mjs b/plugins/client/blaze-client.test.mjs index a4fd640..637944c 100644 --- a/plugins/client/blaze-client.test.mjs +++ b/plugins/client/blaze-client.test.mjs @@ -522,7 +522,17 @@ test("hook payloads stay local even when they contain prompts, paths, manifests, const {client,requests,stateDir}=await fixture(t); const payload={hook_event_name:"UserPromptSubmit",prompt:"SYNTHETIC RAW PROMPT",cwd:"/Users/person/work",package_json:{scripts:{postinstall:"curl example.invalid"}},transcript_path:"/tmp/session.jsonl",session_id:"session-secret"}; const response=await client.hook(payload); - assert.match(response.additionalContext,/did not transmit the user prompt/); + assert.deepEqual(Object.keys(response),["hookSpecificOutput"]); + assert.deepEqual(Object.keys(response.hookSpecificOutput),["hookEventName","additionalContext"]); + assert.equal(response.hookSpecificOutput.hookEventName,"UserPromptSubmit"); + assert.match(response.hookSpecificOutput.additionalContext,/did not transmit the user prompt/); + assert.equal(requests.length,0); + assert.deepEqual(readdirSync(stateDir),[]); +}); + +test("hook output ignores unsupported event names instead of reflecting them", async (t) => { + const {client,requests,stateDir}=await fixture(t); + assert.deepEqual(await client.hook({hook_event_name:"SyntheticPrivateEvent",prompt:"SYNTHETIC RAW PROMPT"}),{}); assert.equal(requests.length,0); assert.deepEqual(readdirSync(stateDir),[]); }); diff --git a/release.json b/release.json index bd61506..97ad531 100644 --- a/release.json +++ b/release.json @@ -1,7 +1,7 @@ { - "version": "0.4.2", - "created_at": "2026-09-07T09:12:23.208Z", - "updated_at": "2026-09-07T09:12:23.208Z", + "version": "0.4.3", + "created_at": "2026-09-07T17:52:20.000Z", + "updated_at": "2026-09-07T17:52:20.000Z", "client_contract": 1, "minimum_client_contract": 0 } diff --git a/skill.md b/skill.md index 93eb205..e4d3d2c 100644 --- a/skill.md +++ b/skill.md @@ -3,7 +3,7 @@ name: blaze description: Reuse and improve verified coding lessons across agents. Use for a nontrivial debugging or implementation problem where an earlier solution could help, when a Blaze offer or receipt appears, or when the user asks to install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition. compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local reminder hooks need no network access. No model provider credentials are needed. metadata: - version: "0.4.2" + version: "0.4.3" --- # Blaze From 2974905ac90f902c409e27b75c8de68dc429354c Mon Sep 17 00:00:00 2001 From: Aymeric Rabot Date: Mon, 7 Sep 2026 13:54:41 -0400 Subject: [PATCH 2/2] Tighten hook event validation --- plugins/claude-code/blaze-client.mjs | 4 +--- .../claude-code/skills/blaze/blaze-client.mjs | 4 +--- plugins/client/blaze-client.mjs | 4 +--- plugins/client/blaze-client.test.mjs | 18 +++++++++++++++++- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/plugins/claude-code/blaze-client.mjs b/plugins/claude-code/blaze-client.mjs index f451235..c76baeb 100644 --- a/plugins/claude-code/blaze-client.mjs +++ b/plugins/claude-code/blaze-client.mjs @@ -39,7 +39,6 @@ const VERIFICATIONS = new Set(["passed", "failed", "not_run", "unknown"]); const CONTRIBUTION_STATES = new Set(["queued", "evaluating", "accepted", "rejected", "failed", "revoked"]); const PARTICIPATION_STATUSES = new Set(["pending", "contributed", "no_novel_solution", "privacy_skip", "verification_missing", "not_solved", "not_applicable"]); const BOUNDARIES = new Set(["task_start_to_agent_end", "task_start_to_verification_end"]); -const ENDS = new Set(["stop", "subagentstop", "sessionend", "session.idle", "sessioncompleted"]); const positiveDuration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 7 * 24 * 60 * 60 * 1000; const wallNow = () => performance.timeOrigin + performance.now(); const DURATION_PATTERN = "(?:0s|<0\\.01s|[0-9]{1,9}\\.[0-9]{1,2}s)"; @@ -466,8 +465,7 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre }, async hook(body) { const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; - if (event !== "UserPromptSubmit" && !(typeof event === "string" && ENDS.has(event.toLowerCase()))) return {}; - if (ENDS.has(event.toLowerCase())) return {}; + if (event !== "UserPromptSubmit") return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, diff --git a/plugins/claude-code/skills/blaze/blaze-client.mjs b/plugins/claude-code/skills/blaze/blaze-client.mjs index f451235..c76baeb 100644 --- a/plugins/claude-code/skills/blaze/blaze-client.mjs +++ b/plugins/claude-code/skills/blaze/blaze-client.mjs @@ -39,7 +39,6 @@ const VERIFICATIONS = new Set(["passed", "failed", "not_run", "unknown"]); const CONTRIBUTION_STATES = new Set(["queued", "evaluating", "accepted", "rejected", "failed", "revoked"]); const PARTICIPATION_STATUSES = new Set(["pending", "contributed", "no_novel_solution", "privacy_skip", "verification_missing", "not_solved", "not_applicable"]); const BOUNDARIES = new Set(["task_start_to_agent_end", "task_start_to_verification_end"]); -const ENDS = new Set(["stop", "subagentstop", "sessionend", "session.idle", "sessioncompleted"]); const positiveDuration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 7 * 24 * 60 * 60 * 1000; const wallNow = () => performance.timeOrigin + performance.now(); const DURATION_PATTERN = "(?:0s|<0\\.01s|[0-9]{1,9}\\.[0-9]{1,2}s)"; @@ -466,8 +465,7 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre }, async hook(body) { const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; - if (event !== "UserPromptSubmit" && !(typeof event === "string" && ENDS.has(event.toLowerCase()))) return {}; - if (ENDS.has(event.toLowerCase())) return {}; + if (event !== "UserPromptSubmit") return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, diff --git a/plugins/client/blaze-client.mjs b/plugins/client/blaze-client.mjs index f451235..c76baeb 100644 --- a/plugins/client/blaze-client.mjs +++ b/plugins/client/blaze-client.mjs @@ -39,7 +39,6 @@ const VERIFICATIONS = new Set(["passed", "failed", "not_run", "unknown"]); const CONTRIBUTION_STATES = new Set(["queued", "evaluating", "accepted", "rejected", "failed", "revoked"]); const PARTICIPATION_STATUSES = new Set(["pending", "contributed", "no_novel_solution", "privacy_skip", "verification_missing", "not_solved", "not_applicable"]); const BOUNDARIES = new Set(["task_start_to_agent_end", "task_start_to_verification_end"]); -const ENDS = new Set(["stop", "subagentstop", "sessionend", "session.idle", "sessioncompleted"]); const positiveDuration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 7 * 24 * 60 * 60 * 1000; const wallNow = () => performance.timeOrigin + performance.now(); const DURATION_PATTERN = "(?:0s|<0\\.01s|[0-9]{1,9}\\.[0-9]{1,2}s)"; @@ -466,8 +465,7 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre }, async hook(body) { const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; - if (event !== "UserPromptSubmit" && !(typeof event === "string" && ENDS.has(event.toLowerCase()))) return {}; - if (ENDS.has(event.toLowerCase())) return {}; + if (event !== "UserPromptSubmit") return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, diff --git a/plugins/client/blaze-client.test.mjs b/plugins/client/blaze-client.test.mjs index 637944c..757c6e1 100644 --- a/plugins/client/blaze-client.test.mjs +++ b/plugins/client/blaze-client.test.mjs @@ -526,13 +526,29 @@ test("hook payloads stay local even when they contain prompts, paths, manifests, assert.deepEqual(Object.keys(response.hookSpecificOutput),["hookEventName","additionalContext"]); assert.equal(response.hookSpecificOutput.hookEventName,"UserPromptSubmit"); assert.match(response.hookSpecificOutput.additionalContext,/did not transmit the user prompt/); + for (const canary of [payload.prompt,payload.cwd,payload.transcript_path,payload.session_id]) { + assert.equal(JSON.stringify(response).includes(canary),false); + } assert.equal(requests.length,0); assert.deepEqual(readdirSync(stateDir),[]); }); test("hook output ignores unsupported event names instead of reflecting them", async (t) => { const {client,requests,stateDir}=await fixture(t); - assert.deepEqual(await client.hook({hook_event_name:"SyntheticPrivateEvent",prompt:"SYNTHETIC RAW PROMPT"}),{}); + for (const hook_event_name of ["SyntheticPrivateEvent",{private:"OBJECT CANARY"},["ARRAY CANARY"],42]) { + const response=await client.hook({hook_event_name,prompt:"SYNTHETIC RAW PROMPT"}); + assert.deepEqual(response,{}); + assert.equal(JSON.stringify(response).includes("CANARY"),false); + } + assert.equal(requests.length,0); + assert.deepEqual(readdirSync(stateDir),[]); +}); + +test("hook defaults a missing event name to the legacy UserPromptSubmit reminder", async (t) => { + const {client,requests,stateDir}=await fixture(t); + const response=await client.hook({prompt:"SYNTHETIC RAW PROMPT"}); + assert.equal(response.hookSpecificOutput.hookEventName,"UserPromptSubmit"); + assert.equal(JSON.stringify(response).includes("SYNTHETIC RAW PROMPT"),false); assert.equal(requests.length,0); assert.deepEqual(readdirSync(stateDir),[]); });