openclaw - ✅(Solved) Fix [Bug]: doctor `--fix` trailer prints unconditionally on clean state when not running with --fix [2 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#80435Fetched 2026-05-11 03:14:38
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
2
Author
Timeline (top)
cross-referenced ×2referenced ×2commented ×1

`openclaw doctor` prints the trailer `Run "openclaw doctor --fix" to apply changes.` whenever the run is not invoked with `--fix`, even when there are no pending config changes to apply; the cue is non-actionable and trains operators to ignore it.

Root Cause

`openclaw doctor` prints the trailer `Run "openclaw doctor --fix" to apply changes.` whenever the run is not invoked with `--fix`, even when there are no pending config changes to apply; the cue is non-actionable and trains operators to ignore it.

Fix Action

Fixed

PR fix notes

PR #80448: fix(doctor): suppress --fix trailer when no pending config changes

Description (problem / solution / changelog)

Problem

runWriteConfigHealth logged a generic trailer whenever !shouldRepair, even on clean runs. On a clean non-repair doctor run, shouldWriteConfig is false and shouldRepair is false, so the message fired misleadingly. When changes existed and user accepted them, the prior head moved the trailer inside the write branch — after replaceConfigFile ran — so the guidance was stale.

Root cause

finalizeDoctorConfigFlow already owns the fix hint: when the user declines pending repairs, it emits fixHints and returns shouldWriteConfig: false. runWriteConfigHealth never needs to re-emit a trailer.

Fix

Remove the --fix trailer from runWriteConfigHealth entirely (plus unused formatCliCommand import). Update tests: trailer never emitted from write-config health regardless of repair/write state.

Proof

Node: v22.13.1

pnpm test src/flows/doctor-health-contributions.test.ts src/commands/doctor/finalize-config-flow.test.ts
Test Files  2 passed (2) | Tests  15 passed (15) | Duration  4.00s

All 4 paths verified:

  • Clean run (shouldWriteConfig=false, shouldRepair=false): no trailer
  • Pending + no --fix (shouldWriteConfig=true, shouldRepair=false): no trailer
  • Pending + --fix (shouldWriteConfig=true, shouldRepair=true): no trailer
  • finalize-config-flow: declined repairs emit fixHints before shouldWriteConfig=false

Changed files

  • src/flows/doctor-health-contributions.test.ts (modified, +93/-0)
  • src/flows/doctor-health-contributions.ts (modified, +0/-4)
  • src/media/image-ops.tempdir.test.ts (modified, +8/-1)

PR #80455: fix(doctor): suppress --fix trailer when no pending config changes remain

Description (problem / solution / changelog)

Summary

  • Problem: openclaw doctor (without --fix) prints Run "openclaw doctor --fix" to apply changes. on every run, even when nothing in the run actually had a pending config change. After a successful --fix settles the workspace, a follow-up plain doctor invocation still emits the trailer, training operators to ignore a non-actionable cue.
  • Why it matters: the trailer is the "you have something to apply" signal. When it fires on a clean state, it makes Doctor complete. ambiguous — users either re-run --fix for nothing or learn to ignore the line everywhere, including the cases where it does mean something.
  • What changed: thread the already-tracked pendingChanges flag from loadAndMaybeMigrateDoctorConfig through the doctor config result, and gate the trailer in runWriteConfigHealth on it. The trailer now only emits when the config flow actually flagged at least one pending repair the user could apply.
  • What did NOT change (scope boundary): no behavior change for --fix runs, no behavior change when pendingChanges is truly set, no edits to other doctor panels or contributions, no rename/move.

Change Type

  • Bug fix

Scope

  • UI / DX (CLI diagnostics output)

Linked Issue/PR

  • Closes #80435
  • This PR fixes a bug or regression

Real behavior proof

  • Behavior or issue addressed: the unconditional Run "openclaw doctor --fix" to apply changes. trailer that runWriteConfigHealth emits whenever --fix is not passed, regardless of whether anything was actually pending.
  • Real environment tested: local macOS (Darwin 24.6.0, Node v24.10.0) checkout of openclaw/openclaw at upstream/main, run with pnpm dev doctor.
  • Exact steps or command run after this patch:
    • pnpm install --frozen-lockfile
    • pnpm test src/flows/doctor-health-contributions.test.ts
    • pnpm check:changed (lint core + tsgo core + tsgo core tests + import-cycles + boundary guards)
    • OPENCLAW_HOME=/tmp/openclaw-trailer-test pnpm dev doctor --fix --non-interactive (× 2 to settle)
    • OPENCLAW_HOME=/tmp/openclaw-trailer-test pnpm dev doctor --non-interactive (the previously failing invocation)
  • Evidence after fix:
    • Unit lane: Test Files 1 passed (1); Tests 11 passed (11) — includes the two new regression cases for doctor:write-config.
    • pnpm check:changed: exits 0 with Found 0 warnings and 0 errors. on lint, clean typecheck on tsgo:core and tsgo:core:test, 0 runtime value cycle(s) from check:import-cycles.
    • Settled clean re-run ends with └ Doctor complete. and no trailing Run "openclaw doctor --fix" to apply changes. line.
  • Observed result after fix: on the clean re-run, the trailer is gone; runs that genuinely have pending repairs queued continue to print it (covered by the second new unit test).
  • What was not tested: I did not reproduce the exact source-checkout + entrypoint-mismatch host layout from the bug report on this machine (no spare gateway-install/migration setup), so the live trailer-on-clean-state observation comes from a fresh OPENCLAW_HOME rather than the reporter's npm-over-pnpm upgrade scenario. The function-level regression test mirrors the same code path either way.

Root Cause

  • Root cause: runWriteConfigHealth reached its else branch whenever shouldWriteConfig resolved to false, and then emitted the trailer based only on whether --fix was missing. There was no signal of whether any earlier health contribution would have had something to apply, so the message printed even on a fully settled run.
  • Missing detection / guardrail: there was no unit test asserting that the trailer is suppressed on a clean run. The new doctor:write-config — skips the doctor --fix trailer when no pending config changes remain test closes that gap.
  • Contributing context (if known): loadAndMaybeMigrateDoctorConfig already tracks pendingChanges locally for finalizeDoctorConfigFlow, but it wasn't surfaced on the returned DoctorConfigResult, so downstream contributions had no way to consult it.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/flows/doctor-health-contributions.test.ts
  • Scenarios locked in:
    • doctor:write-config skips the --fix trailer when configResult.pendingChanges === false and prompter.shouldRepair === false.
    • doctor:write-config still prints the --fix trailer when configResult.pendingChanges === true and prompter.shouldRepair === false.
  • Why this is the smallest reliable guardrail: the bug lives entirely in the single branch in runWriteConfigHealth; exercising both pendingChanges values against the live doctor:write-config contribution is enough to lock in the gate without standing up the whole doctor flow.
  • Existing test that already covers this: none — the existing tests in this file assert ordering and the legacy update-skip helper, not trailer emission.

User-visible / Behavior Changes

openclaw doctor (no --fix) no longer prints Run "openclaw doctor --fix" to apply changes. on runs where no health contribution flagged a pending config change. Runs that do have pending changes are unchanged.

Diagram

Before:
doctor --non-interactive (clean state) -> Doctor complete. + "Run openclaw doctor --fix to apply changes."

After:
doctor --non-interactive (clean state) -> Doctor complete.
doctor --non-interactive (pending change) -> Doctor complete. + "Run openclaw doctor --fix to apply changes."

Security Impact

  • 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

Repro + Verification

Environment

  • OS: macOS (Darwin 24.6.0), reporter saw it on Ubuntu 24.04 aarch64
  • Runtime/container: Node v24.10.0 (reporter: Node v22.22.2)
  • Model/provider: N/A — pure doctor CLI diagnostics
  • Integration/channel (if any): N/A
  • Relevant config (redacted): a settled ~/.openclaw/openclaw.json where the previous --fix exited without Updated ~/.openclaw/openclaw.json

Steps

  1. openclaw doctor --fix --non-interactive until it exits cleanly with no Updated ~/.openclaw/openclaw.json line.
  2. openclaw doctor --non-interactive immediately afterward.
  3. Observe the trailer.

Expected

The settled re-run ends with └ Doctor complete. and no trailing Run "openclaw doctor --fix" to apply changes. line.

Actual (pre-fix)

The settled re-run ends with Run "openclaw doctor --fix" to apply changes. followed by └ Doctor complete., even though no panel reported a pending change.

Evidence

  • Failing-then-passing unit assertions covering both pendingChanges branches (src/flows/doctor-health-contributions.test.ts).
  • Trace/log snippet from pnpm dev doctor --non-interactive on settled OPENCLAW_HOME showing Doctor complete. with no trailer.

Human Verification

  • Verified scenarios:
    • Settled clean re-run of pnpm dev doctor --non-interactive against a freshly populated OPENCLAW_HOME ends with Doctor complete. and no trailer.
    • pnpm test src/flows/doctor-health-contributions.test.ts — 11 passed, including the two new cases.
    • pnpm check:changed — green on lint core, runtime sidecar loader guard, import-cycles, webhook body guard, pairing guards, changelog attributions, dup coverage, tsgo:core, tsgo:core:test, oxlint core.
  • Edge cases checked:
    • pendingChanges === undefined (older test mocks / future callers) — falsy, trailer suppressed; backwards compatible with existing inline-context tests in this file.
    • pendingChanges === true && shouldRepair === false — trailer still prints (covered by the second new unit test).
    • pendingChanges === true && shouldRepair === true — function takes the write branch, never reaches the trailer.
  • What I did not verify: a live reproduction of the original Ubuntu 24.04 aarch64 layout with auditGatewayServiceConfig returning a non-empty issue list, because that requires the reporter's npm-over-pnpm upgrade setup.

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 — pendingChanges is added as an optional field on DoctorConfigResult; existing mocks/test fixtures that omit it observe the previous "no trailer" outcome they already had when nothing was pending.
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: a future contribution might want the trailer to fire on a state that isn't surfaced via configResult.pendingChanges.
    • Mitigation: any new pending state should be threaded through loadAndMaybeMigrateDoctorConfig (now trivial — just OR it into the existing local pendingChanges). The regression test locks the contract for runWriteConfigHealth.

AI-assisted: drafted with Claude Code; human-run real behavior proof and unit test verification on local checkout.

🤖 Generated with Claude Code

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/commands/doctor-config-flow.ts (modified, +1/-0)
  • src/flows/doctor-health-contributions.test.ts (modified, +44/-0)
  • src/flows/doctor-health-contributions.ts (modified, +2/-1)
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

`openclaw doctor` prints the trailer `Run "openclaw doctor --fix" to apply changes.` whenever the run is not invoked with `--fix`, even when there are no pending config changes to apply; the cue is non-actionable and trains operators to ignore it.

Steps to reproduce

  1. On OpenClaw `2026.5.7 (eeef486)` (Ubuntu 24.04 ARM, Node v22.22.2, npm global install), run `openclaw doctor --fix --non-interactive` until it exits cleanly with no `Updated ~/.openclaw/openclaw.json` line and no actionable panels.

  2. Re-run `openclaw doctor --non-interactive` (no `--fix`) immediately afterward.

  3. Observe the final two lines:

    ``` Run "openclaw doctor --fix" to apply changes. └ Doctor complete. ```

The trailer prints even though no panel reported a pending change and the previous `--fix` run did not write a new config.

Expected behavior

The trailer should only print when at least one health contribution recorded a pending change that `--fix` would actually apply. On a clean re-run after `--fix`, the run should end with `Doctor complete.` only.

Actual behavior

The runtime emit site in `dist/doctor-health-contributions-CUSHWwgf.js:393` (`runWriteConfigHealth`) is:

```js if (!ctx.prompter.shouldRepair) ctx.runtime.log(`Run "${formatCliCommand("openclaw doctor --fix")}" to apply changes.`); ```

The line lives in the `else` branch of `runWriteConfigHealth`, but its only guard is `!ctx.prompter.shouldRepair` (i.e. the user did not pass `--fix`). There is no check for whether any pending change was actually recorded during the run. So every doctor invocation without `--fix` emits the trailer, regardless of whether the surrounding health contributions had anything to apply. The same line shows up in the gateway startup log on every restart for the same reason.

OpenClaw version

2026.5.7 (eeef486)

Operating system

Ubuntu 24.04 (aarch64)

Install method

npm global (`/usr/lib/node_modules/openclaw`)

Model

anthropic/claude-sonnet-4.5

Provider / routing chain

openclaw -> anthropic (direct)

Logs, screenshots, and evidence

Clean re-run after a successful `--fix`:

``` $ openclaw doctor --fix --non-interactive ... (run completes; no "Updated ~/.openclaw/openclaw.json" emitted) ... └ Doctor complete.

$ openclaw doctor --non-interactive ... (no panels report pending changes) ... Run "openclaw doctor --fix" to apply changes. └ Doctor complete. ```

Source-of-truth grep on the installed runtime:

``` $ grep -n "to apply changes" /usr/lib/node_modules/openclaw/dist/doctor-health-contributions-CUSHWwgf.js 393: if (!ctx.prompter.shouldRepair) ctx.runtime.log(`Run "${formatCliCommand("openclaw doctor --fix")}" to apply changes.`); ```

Surrounding function (`runWriteConfigHealth`) for context:

```js async function runWriteConfigHealth(ctx) { // ... imports ... if (ctx.configResult.shouldWriteConfig || JSON.stringify(ctx.cfg) !== JSON.stringify(ctx.cfgForPersistence)) { // ... write config, log "Updated …", return ... return; } if (!ctx.prompter.shouldRepair) ctx.runtime.log(`Run "${formatCliCommand("openclaw doctor --fix")}" to apply changes.`); } ```

The `if` covers "there IS something to write"; the trailing log is the "there is nothing to write AND you didn't pass --fix" branch, which is exactly the case where no message should be printed.

Impact and severity

  • Affected: every operator who runs `openclaw doctor` without `--fix` to audit health (and the gateway startup log, which surfaces the same line on every restart).
  • Severity: low (cosmetic), but it degrades the signal-to-noise of `doctor` output: a non-actionable cue on every clean run trains operators to ignore the cue, which makes them more likely to miss it when there ARE pending changes.
  • Frequency: every clean-state `doctor` run (4/4 observed locally on `2026.5.7`).
  • Consequence: doctor output looks like there is always pending work to apply, even on a clean host.

Additional information

Reproduced on a single host (Ubuntu 24.04 ARM, Node 22.22.2, OpenClaw 2026.5.7). The same emit site is reachable from the gateway startup health path, which is why the line also shows up unconditionally in gateway service logs.

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…

FAQ

Expected behavior

The trailer should only print when at least one health contribution recorded a pending change that `--fix` would actually apply. On a clean re-run after `--fix`, the run should end with `Doctor complete.` only.

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 [Bug]: doctor `--fix` trailer prints unconditionally on clean state when not running with --fix [2 pull requests, 1 comments, 2 participants]