openclaw - ✅(Solved) Fix Module-level bannerEmitted flag has no test-reset path, unlike channel-options.ts [1 pull requests, 1 comments, 2 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#83903Fetched 2026-05-20 03:47:07
View on GitHub
Comments
1
Participants
2
Timeline
8
Reactions
1
Timeline (top)
labeled ×5commented ×1cross-referenced ×1unsubscribed ×1

Root Cause

Reproduction

Two successive vitest tests both calling emitCliBanner in the same worker: the second call is silently skipped because bannerEmitted is already true from the first test.

Fix Action

Fix / Workaround

Severity: low / Confidence: high / Category: test-gap Triage: test-gap Detected against: openclaw v2026.5.18 (latest stable at time of scan, 2026-05-18) Tooling: clawpatch 0.3.0 + acpx/claude-sonnet-4-5 via Brad Mills protocol


Standardized clawpatch finding. Persistent in v2026.5.18 (not resolved by upgrading from v2026.5.12). Finding ID: fnd_sig-feat-cli-command-17e3589ff2-_c97bf7ba7a.

PR fix notes

PR #84116: fix(cli): expose banner emit-once reset for tests (#83903)

Description (problem / solution / changelog)

Fixes #83903.

src/cli/banner.ts exports hasEmittedCliBanner() (read-only) but no reset path for the module-level bannerEmitted latch. The latch is correct for production (emit the banner exactly once per process) but blocks multi-scenario vitest specs in the same worker: the second emitCliBanner call silently skips because bannerEmitted is still true from the first. The sibling module src/cli/channel-options.ts already solves this same module-cached-state problem with a __testing.resetPrecomputedChannelOptionsForTests() export — this PR mirrors that pattern.

Changes

  • src/cli/banner.ts: add export const __testing = { resetBannerEmittedForTests(): void { bannerEmitted = false; } } mirroring the channel-options.ts pattern. Production behavior (the emit-once latch) is unchanged.
  • src/cli/banner.test.ts: add two regression cases that drive the latch end-to-end. The first asserts the latch suppresses a second emitCliBanner call in the same scenario (production semantics preserved). The second resets via __testing.resetBannerEmittedForTests() between calls and asserts both emits succeed — the use case the helper enables.

Diff stat: 2 files, +76 / -0.

Real behavior proof

  • Behavior or issue addressed: Sanitized issue evidence — bannerEmitted is declared at module scope, hasEmittedCliBanner() exposes it read-only, but there is no symmetric reset like channel-options.ts:43-47. Any test that calls emitCliBanner twice (or in two scenarios sharing the module cache) has the second call silently skipped.

  • Real environment tested: Local Node 22.x. Probe at /tmp/probe_83903.mjs (a) parses the patched banner.ts and verifies the __testing.resetBannerEmittedForTests export shape, the preserved production latch (bannerEmitted = true; + the early-return guard), and the unchanged hasEmittedCliBanner read-only inspector; and (b) replays the latch semantics — buggy shape (no reset → second emit silently skips), patched shape (reset between calls → both emits succeed), and the production no-reset path (latch still works → second emit suppressed). All assertions pass.

  • Exact steps or command run after this patch: node /tmp/probe_83903.mjs

  • Evidence after fix:

PASS: __testing.resetBannerEmittedForTests exported with the documented body
PASS: production emit-once guard preserved (bannerEmitted=true latch + early return)
PASS: hasEmittedCliBanner read-only inspector unchanged
PASS: replay (buggy / no reset): first emit succeeds; second silently skips because latch persists across tests
PASS: replay (patched / __testing.reset): both emits succeed when reset is called between them — multi-scenario tests can run
PASS: production semantics (no reset between calls): second emit still latches — no behavioral regression

ALL CASES PASS
  • Observed result after fix: Production code paths are byte-identical (same latch, same early-return). Test code can import __testing to reset the latch between scenarios. The two new vitest cases exercise this end-to-end by faking process.stdout.isTTY true, mocking process.stdout.write to capture emissions, calling emitCliBanner twice, asserting the second is suppressed; then calling __testing.resetBannerEmittedForTests() and asserting the second emit succeeds.

  • What was not tested: A real terminal session with the banner emitted twice — the latch behavior is the production guarantee, not something users observe at the CLI. The vitest cases drive the same code path with a faked TTY shim around process.stdout.isTTY.

Audit (per CLAUDE rules — all 5 steps)

  • Existing-helper check: Mirrors the established pattern in src/cli/channel-options.ts:43-52 (__testing export with a single resetXForTests(): void member). No new pattern introduced. PASS
  • Shared-helper caller check: hasEmittedCliBanner has callers in CLI bootstrap code but they only read the latch — adding a sibling reset helper is additive and doesn't change any existing call. PASS
  • Broader-fix rival scan: gh pr list --search '83903 in:title,body' returns no open PRs. PASS
  • Recent-merge audit: git log --oneline -5 -- src/cli/banner.ts shows e1061a8b46 test(live): tolerate provider drift in release checks — unrelated. PASS
  • Prototype-pollution scan: N/A — single boolean assignment.

Changed files

  • src/cli/banner.test.ts (modified, +65/-0)
  • src/cli/banner.ts (modified, +13/-0)

Code Example

let bannerEmitted = false;

---

export function hasEmittedCliBanner(): boolean {
  return bannerEmitted;
}

---

export const __testing = {
  resetPrecomputedChannelOptionsForTests(): void {
    precomputedChannelOptions = undefined;
  },
};
RAW_BUFFERClick to expand / collapse

Severity: low / Confidence: high / Category: test-gap Triage: test-gap Detected against: openclaw v2026.5.18 (latest stable at time of scan, 2026-05-18) Tooling: clawpatch 0.3.0 + acpx/claude-sonnet-4-5 via Brad Mills protocol

Evidence

  • src/cli/banner.ts:47-47 (bannerEmitted)
let bannerEmitted = false;
  • src/cli/banner.ts:180-185 (hasEmittedCliBanner)
export function hasEmittedCliBanner(): boolean {
  return bannerEmitted;
}
  • src/cli/channel-options.ts:43-47 (__testing)
export const __testing = {
  resetPrecomputedChannelOptionsForTests(): void {
    precomputedChannelOptions = undefined;
  },
};

Reasoning

channel-options.ts exports a __testing.resetPrecomputedChannelOptionsForTests() helper for the same pattern (module-level cached state). banner.ts exports hasEmittedCliBanner() (read-only) but provides no reset. Any test that calls emitCliBanner and then needs to verify banner emission in a subsequent scenario within the same module cache will be unable to reset the guard, causing false negatives or test ordering dependencies.

Reproduction

Two successive vitest tests both calling emitCliBanner in the same worker: the second call is silently skipped because bannerEmitted is already true from the first test.

Recommendation

Export a __testing.resetBannerEmittedForTests() helper in src/cli/banner.ts, following the established pattern in channel-options.ts.

Why existing tests miss this

No tests are present for this file group; the gap would be discovered when tests are written.

Suggested regression test

Test: call emitCliBanner, verify hasEmittedCliBanner() is true, call reset, verify hasEmittedCliBanner() is false, call emitCliBanner again, verify banner is emitted a second time.

Minimum fix scope

Add a reset export to src/cli/banner.ts; no behaviour change in production paths.


Standardized clawpatch finding. Persistent in v2026.5.18 (not resolved by upgrading from v2026.5.12). Finding ID: fnd_sig-feat-cli-command-17e3589ff2-_c97bf7ba7a.

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

openclaw - ✅(Solved) Fix Module-level bannerEmitted flag has no test-reset path, unlike channel-options.ts [1 pull requests, 1 comments, 2 participants]