From 5f92094d517b46eebe0f759c9f29e09ddfbc95ae Mon Sep 17 00:00:00 2001 From: Edder Talmor <45949729+EdderTalmor@users.noreply.github.com> Date: Sun, 12 Apr 2026 11:50:58 -0400 Subject: [PATCH] fix: gracefully handle missing QA scenario pack in npm distributions (closes #65082) (#65118) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: allow built-in chat commands to bypass plugins.allow check (closes #65083) The 'commands' CLI command is a built-in chat command registered in the chat commands registry, not a plugin-backed command. When plugins.allow is configured, the error message incorrectly suggests adding 'commands' to plugins.allow, which produces a second error because no 'commands' plugin exists. Check if the command has a plugin entry or manifest alias before suggesting plugins.allow. Built-in commands without plugin entries now proceed normally instead of showing misleading errors. * fix: gracefully handle missing QA scenario pack in npm distributions (closes #65082) The completion cache update fails with a fatal error when the qa/scenarios/index.md file is not present in the installed npm package, even though the directory is listed in package.json "files". Instead of throwing an error, return an empty QA scenario pack with default agent identity. This allows completion cache updates to succeed while QA scenarios remain unavailable in the npm distribution. The QA scenario pack is primarily used for internal testing and QA automation — it is not critical for end-user functionality. * revert: remove unintended run-main.ts changes from PR #65118 The scenario-catalog.ts fix is the correct change for this PR. The run-main.ts changes were accidentally included and cause a regression in plugins.allow error handling. * fix(qa): tolerate missing packaged scenario config --------- Co-authored-by: Vincent Koc --- CHANGELOG.md | 1 + extensions/qa-lab/src/scenario-catalog.test.ts | 4 ++++ extensions/qa-lab/src/scenario-catalog.ts | 12 ++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d51f5383c70..cc7dc8b5ccd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai - Gateway/plugins: always send a non-empty `idempotencyKey` for plugin subagent runs, so dreaming narrative jobs stop failing gateway schema validation. (#65354) Thanks @CodeForgeNet and @vincentkoc. - Cron/isolated sessions: persist the right transcript path for each isolated run, including fresh session rollovers, so cron runs stop appending to stale session files. Thanks @samrusani and @vincentkoc. - Dreaming/cron: wake managed dreaming jobs immediately instead of waiting for the next heartbeat, so scheduled dreaming runs start when the cron fires. (#65053) Thanks @l0cka and @vincentkoc. +- QA/packaging: stop packaged QA helpers from crashing when optional scenario execution config is unavailable, so npm distributions can skip the repo-only scenario pack without breaking completion-cache and startup paths. (#65118) Thanks @EdderTalmor and @vincentkoc. ## 2026.4.11 diff --git a/extensions/qa-lab/src/scenario-catalog.test.ts b/extensions/qa-lab/src/scenario-catalog.test.ts index cd5ea2682c9..13b670e44dd 100644 --- a/extensions/qa-lab/src/scenario-catalog.test.ts +++ b/extensions/qa-lab/src/scenario-catalog.test.ts @@ -119,4 +119,8 @@ describe("qa scenario catalog", () => { }), ).toThrow(/gracefulFallbackAny entries must be strings/); }); + + it("returns undefined execution config for an unknown scenario id", () => { + expect(readQaScenarioExecutionConfig("missing-scenario-id")).toBeUndefined(); + }); }); diff --git a/extensions/qa-lab/src/scenario-catalog.ts b/extensions/qa-lab/src/scenario-catalog.ts index ffaca54946e..c20161a31ef 100644 --- a/extensions/qa-lab/src/scenario-catalog.ts +++ b/extensions/qa-lab/src/scenario-catalog.ts @@ -278,7 +278,15 @@ export function readQaScenarioPackMarkdown(): string { export function readQaScenarioPack(): QaScenarioPack { const packMarkdown = readTextFile(QA_SCENARIO_PACK_INDEX_PATH).trim(); if (!packMarkdown) { - throw new Error(`qa scenario pack not found: ${QA_SCENARIO_PACK_INDEX_PATH}`); + // The QA scenario pack is optional in npm distributions. Return an empty + // pack so completion cache updates and other consumers don't crash when + // the qa/scenarios/ directory is not shipped with the package. + return { + version: 1, + agent: { identityMarkdown: DEFAULT_QA_AGENT_IDENTITY_MARKDOWN }, + kickoffTask: "QA scenarios not available in this distribution.", + scenarios: [], + }; } const parsedPack = parseQaYamlWithContext( qaScenarioPackSchema, @@ -344,7 +352,7 @@ export function readQaScenarioById(id: string): QaSeedScenario { } export function readQaScenarioExecutionConfig(id: string): Record | undefined { - return readQaScenarioById(id).execution?.config; + return readQaScenarioPack().scenarios.find((candidate) => candidate.id === id)?.execution?.config; } export function validateQaScenarioExecutionConfig(config: Record) {