openclaw - ✅(Solved) Fix CLI initialization crashes in production due to hardcoded QA scenario dependency [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#64522Fetched 2026-04-11 06:14:37
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Timeline (top)
cross-referenced ×1referenced ×1

In OpenClaw v2026.4.9, running openclaw completion (and potentially other commands) crashes immediately with:

[openclaw] Failed to start CLI: Error: qa scenario pack not found: qa/scenarios/index.md

Error Message

[openclaw] Failed to start CLI: Error: qa scenario pack not found: qa/scenarios/index.md

Root Cause

A top-level module in the CLI's compiled JavaScript (suite-BW4kSK9C.js) calls readQaScenarioExecutionConfig("source-docs-discovery-report") during the command tree registration process.

Because production builds do not include the qa/ directory, this immediately throws an error and prevents the CLI from starting. Attempting to bypass this by creating dummy files just results in deeper ZodError validation failures.

Fix Action

Workaround

Patching the compiled JS to replace the call with undefined allows it to fall back to defaults and successfully run.

sed -i 's/readQaScenarioExecutionConfig("source-docs-discovery-report")?.requiredFiles/undefined/' <path_to_openclaw_dist>/suite-BW4kSK9C.js

PR fix notes

PR #64594: fix(qa-lab): do not crash CLI startup when qa scenario pack is absent

Description (problem / solution / changelog)

Summary

  • Problem: openclaw completion (and potentially other CLI entry points) crashes immediately on production installs with Error: qa scenario pack not found: qa/scenarios/index.md. Reported in #64522.
  • Why it matters: This is a CLI-unusable-in-production bug. Any user who installs a distribution without the qa/ directory (which is intentionally excluded from packaged builds) gets a dead CLI — no command registration, no shell completion, no graceful fallback. The crash happens before openclaw --help can even print.
  • What changed: Return undefined from readQaScenarioExecutionConfig when the scenario pack index is not shipped on disk, instead of throwing. Add an exported isQaScenarioPackAvailable() helper for the check. The caller in discovery-eval.ts already has a fallback-to-defaults path (config?.requiredFiles ?? [hardcoded defaults]) that was written in anticipation of this exact case — we just weren't reaching it because the library layer threw first.
  • What did NOT change (scope boundary): readQaScenarioPack(), readQaScenarioById(), readQaBootstrapScenarioCatalog(), and the YAML parse paths all continue to throw loudly on malformed packs, unknown scenario ids, missing fences, and zod validation failures. Only the "pack file is not present on disk at all" case is softened. discovery-eval.ts itself is not modified — once the library returns undefined, the existing fallback pattern takes over.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #64522
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: extensions/qa-lab/src/discovery-eval.ts line 17 runs readQaScenarioExecutionConfig("source-docs-discovery-report") at module-load time via a top-level constant initializer:

    const REQUIRED_DISCOVERY_REFS = readRequiredDiscoveryRefs();

    readQaScenarioExecutionConfig calls readQaScenarioByIdreadQaScenarioPack, which calls readTextFile(QA_SCENARIO_PACK_INDEX_PATH). In production builds the qa/ directory is not shipped, so resolveRepoPath returns null, readTextFile returns "", and readQaScenarioPack throws qa scenario pack not found: qa/scenarios/index.md. That throw propagates up the module graph and crashes the CLI before command registration finishes.

  • Missing detection / guardrail: readQaScenarioExecutionConfig's declared return type is already Record<string, unknown> | undefined, signaling that callers should handle undefined. But the only undefined-returning path was the inner execution?.config chain — the outer "pack file missing" case threw instead. The caller in discovery-eval.ts was written under the (reasonable) assumption that a missing pack would produce undefined, and wrote the ?? [hardcoded defaults] fallback accordingly. That fallback was never reached because the exception short-circuited it.

  • Contributing context (if known): The bundled CLI (suite-BW4kSK9C.js in the reporter's compiled output) imports the qa-lab extension's command tree as part of registration, and any module in that import chain running a throwing top-level statement takes down the whole CLI. The reporter independently found a sed-based workaround (s/readQaScenarioExecutionConfig("source-docs-discovery-report")?.requiredFiles/undefined/) that proves the fallback defaults are fine — which they are, since they match the current qa/scenarios/index.md contents at the relevant keys.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/qa-lab/src/scenario-catalog.test.ts (new describe block: "when the pack is not shipped")
  • Scenario the test should lock in: Four cases:
    1. Sanity: isQaScenarioPackAvailable() returns true in the dev repo (no fs stub).
    2. isQaScenarioPackAvailable() returns false when fs.existsSync is stubbed via vi.spyOn to simulate the production-build state where the qa/ directory is absent.
    3. readQaScenarioExecutionConfig("source-docs-discovery-report") returns undefined (instead of throwing) when the pack is absent.
    4. The bug itself: import("./discovery-eval.js") under stubbed-absent-pack resolves successfully, with reportsMissingDiscoveryFiles still functional and using the hardcoded defaults. This is the user-visible regression test — it verifies the CLI startup import path no longer crashes in production builds.
  • Why this is the smallest reliable guardrail: The library layer is pure enough that vi.spyOn(fs, "existsSync") is sufficient to simulate the production state without a sandbox tempdir or real filesystem manipulation. The guardrail is a truth table over the isQaScenarioPackAvailable() values, and the import-time test specifically locks in the no-crash-on-load invariant that failed in #64522.
  • Existing test that already covers this (if any): None. The existing tests in scenario-catalog.test.ts all run in the dev repo where the pack is present, so the "pack absent" code path was never exercised.
  • If no new test is added, why not: N/A — four new tests added.

User-visible / Behavior Changes

  • Users installing OpenClaw in production environments (where qa/ is excluded) will now be able to start the CLI normally. openclaw completion, openclaw --help, and every other command should work rather than crashing at module load.
  • In the degraded state (pack absent), discovery-eval's REQUIRED_DISCOVERY_REFS falls back to its hardcoded defaults (repo/qa/scenarios/index.md, repo/extensions/qa-lab/src/suite.ts, repo/docs/help/testing.md) instead of the pack-supplied list. These are QA-surface defaults; since the pack itself isn't shipped, the discovery-eval functions are in a no-op state for production users anyway, and any future code that actually runs QA discovery will need the pack to be installed explicitly.
  • No change in dev environments or for anyone who ships the qa/ directory alongside their install.

Diagram (if applicable)

Before:
  CLI startup
    -> import @openclaw/qa-lab
    -> discovery-eval.ts top-level init
    -> readQaScenarioExecutionConfig("source-docs-discovery-report")
    -> readQaScenarioPack()
    -> readTextFile("qa/scenarios/index.md") -> "" (file missing)
    -> throw "qa scenario pack not found: ..."
    -> CLI crash before any command registers

After:
  CLI startup
    -> import @openclaw/qa-lab
    -> discovery-eval.ts top-level init
    -> readQaScenarioExecutionConfig("source-docs-discovery-report")
    -> isQaScenarioPackAvailable() -> false (no pack file resolved)
    -> return undefined
    -> discovery-eval.ts: config?.requiredFiles ?? [hardcoded defaults]
    -> REQUIRED_DISCOVERY_REFS = [hardcoded defaults]
    -> module loads cleanly, CLI continues normal registration

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No — the only new filesystem read is the existence check in isQaScenarioPackAvailable(), which uses the same resolveRepoPath walk-up already used everywhere else in this file.

Repro + Verification

Environment

  • OS: tested on Windows 11, Node 22
  • Runtime/container: vitest run against the local checkout
  • Model/provider: N/A
  • Integration/channel: qa-lab extension CLI import path
  • Relevant config: production build (no qa/ directory)

Steps

  1. Stub fs.existsSync to return false for any candidate walk-up path (simulating a production build where qa/ is not shipped).
  2. Call isQaScenarioPackAvailable() — expect false.
  3. Call readQaScenarioExecutionConfig("source-docs-discovery-report") — expect undefined (not a throw).
  4. await import("./discovery-eval.js") — expect the module to load successfully with its hardcoded fallback refs in effect.

Expected

  • No throw. discovery-eval functions load and remain usable.

Actual

  • Matches expected. Covered by the new tests in scenario-catalog.test.ts.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Test output after the fix:

 RUN  v4.1.4
 Test Files  1 passed (1)
      Tests  8 passed (8)

Previously the scenario-catalog.test.ts file had 4 tests (all covering the happy path with the pack present). This PR grows it to 8: 4 existing + 4 new for the absent-pack fallback.

Also verified:

  • discovery-eval.test.ts still green after the change (13/13 pass).
  • oxlint on both touched files: Found 0 warnings and 0 errors.

Human Verification (required)

  • Verified scenarios:
    • Ran scenario-catalog.test.ts locally: 8/8 pass.
    • Ran discovery-eval.test.ts locally: 13/13 pass (unchanged behavior).
    • Stepped through the resolveRepoPath walk-up logic to confirm that a returned null correctly collapses to an empty-string readTextFile result, and that the new isQaScenarioPackAvailable() short-circuit covers that case without double-I/O in the happy path (the check is O(walk-up) and then the actual read does the same walk, which is acceptable for a non-hot-path function).
    • Ran oxlint on both touched files: clean.
    • Stashed the branch and confirmed the pre-existing qa-agent-workspace.test.ts symlink failure on Windows is not caused by this change — it fails identically on clean main because fs.symlink requires admin/developer-mode on Windows. Called out here to head off confusion if CI on Linux shows that test passing while a local Windows run shows it red.
  • Edge cases checked:
    • Pack absent → undefined (intended).
    • Pack present, but id unknown → still throws unknown qa scenario: <id> via readQaScenarioById (intended — caller bug).
    • Pack present, YAML malformed → still throws from zod parse (intended — pack bug).
    • Pack present, fence missing → still throws from the fence-extract helper (intended — pack bug).
  • What I did NOT verify:
    • I did not physically build a production dist, rip out the qa/ directory, and run openclaw completion against it. The simulated-absence test via vi.spyOn(fs, "existsSync") exercises the same resolveRepoPath path the reporter's crash goes through, which is sufficient evidence for a unit-test-level lock-in.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — existing dev and installed-with-pack environments behave identically. Only the currently-broken production-without-pack case changes behavior (from "crash" to "start with fallback defaults").
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: A future developer might assume readQaScenarioExecutionConfig always returns a non-undefined value and introduce a NPE at a new call site.
    • Mitigation: The declared return type is already Record<string, unknown> | undefined, so TypeScript enforces the nullability. The existing caller in discovery-eval.ts already uses ?. chaining and ?? fallback correctly.
  • Risk: A malformed pack (bad YAML, missing fence, failed zod) could previously also produce this same qa scenario pack not found text if somehow readTextFile returned empty for a corrupt file. We're now silent in that narrow case.
    • Mitigation: readTextFile returns empty only when resolveRepoPath returns null (file does not exist at all). A file that exists but contains garbage would return the garbage string, which then flows into the YAML/zod path and throws loudly. So the "silent fallback" is strictly scoped to "file not present on disk."
  • Risk: Doubling the filesystem walk in the happy path (isQaScenarioPackAvailable() + readQaScenarioById() both walk up to find the pack).
    • Mitigation: Each walk is a handful of fs.existsSync / fs.statSync calls, which are microseconds on any modern filesystem. This function is not on a hot path — it runs once per discovery-eval module load. The clarity of the early-return guard outweighs the micro-cost.

Changed files

  • extensions/qa-lab/src/scenario-catalog.test.ts (modified, +44/-1)
  • extensions/qa-lab/src/scenario-catalog.ts (modified, +25/-0)

Code Example

[openclaw] Failed to start CLI: Error: qa scenario pack not found: qa/scenarios/index.md

---

sed -i 's/readQaScenarioExecutionConfig("source-docs-discovery-report")?.requiredFiles/undefined/' <path_to_openclaw_dist>/suite-BW4kSK9C.js
RAW_BUFFERClick to expand / collapse

Description

In OpenClaw v2026.4.9, running openclaw completion (and potentially other commands) crashes immediately with:

[openclaw] Failed to start CLI: Error: qa scenario pack not found: qa/scenarios/index.md

Root Cause

A top-level module in the CLI's compiled JavaScript (suite-BW4kSK9C.js) calls readQaScenarioExecutionConfig("source-docs-discovery-report") during the command tree registration process.

Because production builds do not include the qa/ directory, this immediately throws an error and prevents the CLI from starting. Attempting to bypass this by creating dummy files just results in deeper ZodError validation failures.

Workaround

Patching the compiled JS to replace the call with undefined allows it to fall back to defaults and successfully run.

sed -i 's/readQaScenarioExecutionConfig("source-docs-discovery-report")?.requiredFiles/undefined/' <path_to_openclaw_dist>/suite-BW4kSK9C.js

Suggested Fix

Ensure QA-specific configuration checks are either evaluated lazily (only when a QA command is actively invoked) or are wrapped in an environment check so they don't break production builds where the qa/ directory is intentionally excluded.

extent analysis

TL;DR

The most likely fix is to modify the code to lazily evaluate QA-specific configuration checks or wrap them in environment checks to prevent crashes in production builds.

Guidance

  • Identify and modify the top-level module in the CLI's compiled JavaScript that calls readQaScenarioExecutionConfig to evaluate the QA scenario pack lazily or conditionally based on the environment.
  • Verify that the qa/ directory is correctly excluded from production builds to prevent unnecessary file system checks.
  • Consider implementing a fallback mechanism to handle cases where the QA scenario pack is not found, such as returning a default configuration or throwing a more informative error.
  • Review the code for similar instances of QA-specific configuration checks that may also need to be modified.

Example

// Before
readQaScenarioExecutionConfig("source-docs-discovery-report")?.requiredFiles

// After (lazy evaluation)
if (process.env.NODE_ENV !== 'production') {
  readQaScenarioExecutionConfig("source-docs-discovery-report")?.requiredFiles
}

Notes

This fix assumes that the QA scenario pack is not required for production builds. If the pack is necessary, an alternative solution will be needed to include it in the production build or provide a suitable fallback.

Recommendation

Apply workaround: patch the compiled JS to replace the call with undefined as a temporary solution until the underlying code can be modified to handle QA-specific configuration checks correctly. This will allow the CLI to start and run commands without crashing due to the missing QA scenario pack.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING