Skip to content

Commit 7d82e5b

Browse files
authored
Doctor: audit lint default selection (#100361)
Summary: - audit Doctor lint default selection after the full lint-family backfill - make legacy state, skills readiness, session transcripts, and session snapshots explicit-only for default lint - document default lint vs --all/--only and keep plugin/SDK public contracts unchanged Validation: - Galin review: no blocking findings - maintainer accepted the default-lint compatibility tradeoff in PR comment - exact-head hosted gates passed for d5d88a0: CI#28976811444 and Workflow Sanity#28976811343 - local broad pnpm check was blocked by a shrinkwrap guard failure that reproduces on origin/main and is unrelated to this five-file Doctor diff Co-authored-by: Gio Della-Libera <[email protected]>
1 parent e8c1780 commit 7d82e5b

5 files changed

Lines changed: 45 additions & 5 deletions

File tree

docs/gateway/doctor.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,37 @@ cat ~/.openclaw/openclaw.json
8080

8181
## Read-only lint mode
8282

83-
`openclaw doctor --lint` is the automation-friendly sibling of `openclaw doctor --fix`. Both run the same health checks; only the posture differs:
83+
`openclaw doctor --lint` is the automation-friendly sibling of
84+
`openclaw doctor --fix`. They share the same Doctor rule registry, but they do
85+
not select or act on rules in the same way:
8486

8587
| Mode | Prompts | Writes config/state | Output | Use it for |
8688
| ------------------------ | --------- | ----------------------- | ---------------------- | ------------------------------- |
8789
| `openclaw doctor` | yes | no | friendly health report | a human checking status |
8890
| `openclaw doctor --fix` | sometimes | yes, with repair policy | friendly repair log | applying approved repairs |
8991
| `openclaw doctor --lint` | no | no | structured findings | CI, preflight, and review gates |
9092

91-
Health checks may provide an optional `repair()` implementation; `doctor --fix` applies it when present and falls back to the legacy doctor repair flow otherwise. The contract separates `detect()` (reports findings) from `repair()` (reports changes/diffs/side effects), which keeps a path open for a future `doctor --fix --dry-run` without turning lint checks into mutation planners.
93+
Default `doctor --lint` runs the broad-safe automation profile: checks that are
94+
static, local, and useful in CI or preflight output. It skips opt-in checks that
95+
are advisory, environment-sensitive, live-service dependent, account/workspace
96+
inventory, or historical cleanup. Use `doctor --lint --all` when you want the
97+
full registered lint audit, including those opt-in checks, or `--only <id>` for
98+
a targeted check.
99+
100+
`doctor --fix` does not use the lint default profile and does not accept
101+
`--all`. It runs Doctor's ordered repair path: modern health checks may provide
102+
an optional `repair()` implementation, and older areas still use their legacy
103+
Doctor repair flow. Some lint findings are intentionally diagnostic only, so a
104+
check appearing in `--lint --all` does not mean `--fix` will mutate that area.
105+
The contract separates `detect()` (reports findings) from `repair()` (reports
106+
changes/diffs/side effects), which keeps a path open for a future
107+
`doctor --fix --dry-run` without turning lint checks into mutation planners.
108+
109+
Some built-in checks are default-disabled internally so they stay available to
110+
`--all`, `--only`, and Doctor repair flows without becoming part of the default
111+
`doctor --lint` automation profile. Finding severity is still emitted per
112+
finding (`info`, `warning`, or `error`); default selection is not a severity
113+
level.
92114

93115
```bash
94116
openclaw doctor --lint
@@ -115,7 +137,7 @@ Exit codes:
115137
Flags:
116138

117139
- `--severity-min info|warning|error` (default `warning`): controls both what prints and what causes a non-zero exit.
118-
- `--all`: runs every registered check, including opt-in checks excluded from the default automation set.
140+
- `--all`: runs every registered lint check, including opt-in checks excluded from the default automation set.
119141
- `--only <id>` (repeatable): run only the named check id(s); an unknown id is reported as an error finding.
120142
- `--skip <id>` (repeatable): exclude a check while keeping the rest of the run active.
121143
- `--json`, `--severity-min`, `--all`, `--only`, and `--skip` require `--lint`; plain `openclaw doctor` and `--fix` runs reject them.

src/flows/doctor-core-checks.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ describe("CORE_HEALTH_CHECKS", () => {
357357
"core/doctor/skills-readiness",
358358
);
359359

360+
expect(check).toMatchObject({ defaultEnabled: false });
360361
expect(check["repair"]).toBeTypeOf("function");
361362

362363
const findings = await check.detect({

src/flows/doctor-core-checks.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,11 +422,12 @@ const hooksModelCheck: HealthCheck = {
422422
},
423423
};
424424

425-
const legacyStateCheck: HealthCheck = {
425+
const legacyStateCheck: HealthCheck & { readonly defaultEnabled: false } = {
426426
id: "core/doctor/legacy-state",
427427
kind: "core",
428428
description: "Legacy sessions, agent state, and channel auth paths have been migrated.",
429429
source: "doctor",
430+
defaultEnabled: false,
430431
async detect(ctx) {
431432
const { detectLegacyStateMigrations } = await import("../commands/doctor-state-migrations.js");
432433
const detected = await detectLegacyStateMigrations({ cfg: ctx.cfg });
@@ -874,12 +875,15 @@ const browserCheck: HealthCheck = {
874875
},
875876
};
876877

877-
function createSkillsReadinessCheck(deps: CoreHealthCheckDeps): HealthCheck {
878+
function createSkillsReadinessCheck(
879+
deps: CoreHealthCheckDeps,
880+
): HealthCheck & { readonly defaultEnabled: false } {
878881
return {
879882
id: "core/doctor/skills-readiness",
880883
kind: "core",
881884
description: "Allowed skills are usable in the current runtime environment.",
882885
source: "doctor",
886+
defaultEnabled: false,
883887
async detect(ctx, scope) {
884888
const unavailable = filterUnavailableSkillsForScope(
885889
await deps.detectUnavailableSkills(ctx.cfg),

src/flows/doctor-health-contributions.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,6 +1428,11 @@ describe("doctor health contributions", () => {
14281428

14291429
it("passes the active config into legacy state migration", async () => {
14301430
const contribution = requireDoctorContribution("doctor:legacy-state");
1431+
const legacyStateCheck = CORE_HEALTH_CHECKS.find(
1432+
(check) => check.id === "core/doctor/legacy-state",
1433+
);
1434+
expect(legacyStateCheck).toMatchObject({ defaultEnabled: false });
1435+
14311436
const cfg = { session: { store: "/tmp/shared-sessions.json" } };
14321437
const detected = { preview: ["legacy sessions"], warnings: [] };
14331438
mocks.detectLegacyStateMigrations.mockResolvedValue(detected);
@@ -1570,6 +1575,12 @@ describe("doctor health contributions", () => {
15701575
expect(contributionIds).toContain("core/doctor/config-audit-scrub");
15711576
expect(contributionIds).toContain("core/doctor/session-transcripts");
15721577
expect(contributionIds).toContain("core/doctor/session-snapshots");
1578+
expect(
1579+
contributionChecks.find((check) => check.id === "core/doctor/session-transcripts"),
1580+
).toMatchObject({ defaultEnabled: false });
1581+
expect(
1582+
contributionChecks.find((check) => check.id === "core/doctor/session-snapshots"),
1583+
).toMatchObject({ defaultEnabled: false });
15731584
expect(contributionIds).toContain("core/doctor/plugin-registry");
15741585
expect(contributionIds).toContain("core/doctor/configured-plugin-installs");
15751586
expect(contributionIds).toContain("core/doctor/legacy-plugin-dependencies");

src/flows/doctor-health-contributions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,6 +1769,7 @@ export function resolveDoctorHealthContributions(): DoctorHealthContribution[] {
17691769
healthChecks: {
17701770
id: "core/doctor/session-transcripts",
17711771
description: "Legacy or branchy session transcript files are represented as findings.",
1772+
defaultEnabled: false,
17721773
async detect() {
17731774
const { detectSessionTranscriptHealthIssues, sessionTranscriptIssueToHealthFinding } =
17741775
await import("../commands/doctor-session-transcripts.js");
@@ -1801,6 +1802,7 @@ export function resolveDoctorHealthContributions(): DoctorHealthContribution[] {
18011802
healthChecks: {
18021803
id: "core/doctor/session-snapshots",
18031804
description: "Stale cached session snapshot paths are represented as findings.",
1805+
defaultEnabled: false,
18041806
async detect(ctx) {
18051807
const { detectSessionSnapshotHealthIssues, sessionSnapshotIssueToHealthFinding } =
18061808
await import("../commands/doctor-session-snapshots.js");

0 commit comments

Comments
 (0)