Skip to content

lcx-doctor falsely reports Windows-only hooks as drift on Linux #157

Description

@L-Batt

Problem Situation

lcx-doctor compared the installed Linux manifest directly with the cross-platform source manifest and warned that two Git Bash MCP hook registrations were missing.

Reproduction Logs

Command: node --test plugins/omo/test/lcx-bug-skills.test.mjs

Before the fix: 1 passed, 1 failed. The regression failed because lcx-doctor had no removeGitBashHooksOffWindows or non-Windows expected-materialization rule.

Root Cause

The installer intentionally removes exactly pre-tool-use-recommending-git-bash-mcp.json and post-compact-resetting-git-bash-mcp-reminder.json when platform is not win32. The doctor treated that platform-conditioned manifest as payload drift instead of expected materialization. Published 4.19.4 source and payload are intact.

Verified Fix

Document the installer transformation in both generated skill copies, classify the two omissions as PASS context on non-Windows, preserve the requirement on Windows, and lock the rule with a failing-first regression test.

diff --git a/plugins/omo/components/lcx/skills/lcx-doctor/SKILL.md b/plugins/omo/components/lcx/skills/lcx-doctor/SKILL.md
index c8b0afe..55d8864 100644
--- a/plugins/omo/components/lcx/skills/lcx-doctor/SKILL.md
+++ b/plugins/omo/components/lcx/skills/lcx-doctor/SKILL.md
@@ -71,9 +71,10 @@ sync_latest_source openai/codex "$LAZYCODEX_SOURCE_ROOT/openai-codex-source"
    - Plugin payload present and non-empty: read `.codex-plugin/plugin.json`; when that manifest declares a `hooks` array, validate every direct hook path declared by the manifest; require `hooks/hooks.json` only when the manifest declares it; do not require retired paths such as `components/workflow-selector` or `hooks/user-prompt-submit-selecting-lazycodex-workflow.json` unless the current manifest declares them.
    - Verify the manifest-declared runtime payload, not a remembered source tree. Current payload includes `skills/`, `.mcp.json`, root CLI runtimes such as `dist/cli/index.js` and `dist/cli-node/index.js`, and every hook/MCP `components/*/dist/*.js` target referenced by installed manifests.
    - Treat install-time materialization rewrites as expected when the rewritten target exists and is non-empty. For example, `.mcp.json` may use plugin-local or absolute installed paths for CodeGraph/MCP runtimes; that is PASS/WARN context, not payload drift. Missing or zero-byte rewritten targets are FAIL.
+   - On non-Windows hosts, `removeGitBashHooksOffWindows` intentionally removes `./hooks/pre-tool-use-recommending-git-bash-mcp.json` and `./hooks/post-compact-resetting-git-bash-mcp-reminder.json` from the installed manifest. Their absence is expected materialization and PASS context, not hook drift; do not restore or activate them. On Windows, require both paths when the source manifest declares them.
    - Stale project-local leftovers the installer now removes (e.g. `.codex/hooks.json`, `.codex/skills` in the project) are flagged, not deleted.
 4. Probe the real surface. Do not invoke `lazycodex doctor`; this skill is already running inside that doctor workflow, so calling it would recurse. Instead run non-recursive probes directly: `codex --version`, `command -v codex`, the bin-link checks above, config/plugin payload inspections, and a trivial non-interactive Codex invocation that loads the plugin. Use the configured Codex default model for the runtime probe unless the user explicitly passed a model override to the doctor surface; never force a guessed/rejected model such as `gpt-5.5-codex-mini`. Capture stderr verbatim; a clean exit with warnings is WARN, not PASS.
-5. Compare for drift. Where installed manifest-declared bundled files differ from the same files at the installed version, or the latest source removed or renamed something the local config still references, record it with both paths. Do not report expected materialization differences, such as absolute `.mcp.json` runtime paths, as drift when their targets exist and are non-empty.
+5. Compare for drift. Where installed manifest-declared bundled files differ from the same files at the installed version, or the latest source removed or renamed something the local config still references, record it with both paths. Do not report expected materialization differences, such as absolute `.mcp.json` runtime paths or the non-Windows Git Bash hook removal above, as drift when their platform-specific invariants hold.
 6. Check whether each FAIL is already known: `gh issue list --repo code-yeongyu/lazycodex --search "<short symptom>" --state open` (and `openai/codex` when the failure points upstream). Link matches in the report instead of re-diagnosing from scratch.
 7. If a probe fails and the cause is not explained by config or source comparison, invoke `$omo:debugging` for the investigation. If Codex exposes only unqualified skill names in the current session, invoke `$debugging` and state that it is the OMO debugging skill.
 8. Emit the report.
diff --git a/plugins/omo/skills/lcx-doctor/SKILL.md b/plugins/omo/skills/lcx-doctor/SKILL.md
index c8b0afe..55d8864 100644
--- a/plugins/omo/skills/lcx-doctor/SKILL.md
+++ b/plugins/omo/skills/lcx-doctor/SKILL.md
@@ -71,9 +71,10 @@ sync_latest_source openai/codex "$LAZYCODEX_SOURCE_ROOT/openai-codex-source"
    - Plugin payload present and non-empty: read `.codex-plugin/plugin.json`; when that manifest declares a `hooks` array, validate every direct hook path declared by the manifest; require `hooks/hooks.json` only when the manifest declares it; do not require retired paths such as `components/workflow-selector` or `hooks/user-prompt-submit-selecting-lazycodex-workflow.json` unless the current manifest declares them.
    - Verify the manifest-declared runtime payload, not a remembered source tree. Current payload includes `skills/`, `.mcp.json`, root CLI runtimes such as `dist/cli/index.js` and `dist/cli-node/index.js`, and every hook/MCP `components/*/dist/*.js` target referenced by installed manifests.
    - Treat install-time materialization rewrites as expected when the rewritten target exists and is non-empty. For example, `.mcp.json` may use plugin-local or absolute installed paths for CodeGraph/MCP runtimes; that is PASS/WARN context, not payload drift. Missing or zero-byte rewritten targets are FAIL.
+   - On non-Windows hosts, `removeGitBashHooksOffWindows` intentionally removes `./hooks/pre-tool-use-recommending-git-bash-mcp.json` and `./hooks/post-compact-resetting-git-bash-mcp-reminder.json` from the installed manifest. Their absence is expected materialization and PASS context, not hook drift; do not restore or activate them. On Windows, require both paths when the source manifest declares them.
    - Stale project-local leftovers the installer now removes (e.g. `.codex/hooks.json`, `.codex/skills` in the project) are flagged, not deleted.
 4. Probe the real surface. Do not invoke `lazycodex doctor`; this skill is already running inside that doctor workflow, so calling it would recurse. Instead run non-recursive probes directly: `codex --version`, `command -v codex`, the bin-link checks above, config/plugin payload inspections, and a trivial non-interactive Codex invocation that loads the plugin. Use the configured Codex default model for the runtime probe unless the user explicitly passed a model override to the doctor surface; never force a guessed/rejected model such as `gpt-5.5-codex-mini`. Capture stderr verbatim; a clean exit with warnings is WARN, not PASS.
-5. Compare for drift. Where installed manifest-declared bundled files differ from the same files at the installed version, or the latest source removed or renamed something the local config still references, record it with both paths. Do not report expected materialization differences, such as absolute `.mcp.json` runtime paths, as drift when their targets exist and are non-empty.
+5. Compare for drift. Where installed manifest-declared bundled files differ from the same files at the installed version, or the latest source removed or renamed something the local config still references, record it with both paths. Do not report expected materialization differences, such as absolute `.mcp.json` runtime paths or the non-Windows Git Bash hook removal above, as drift when their platform-specific invariants hold.
 6. Check whether each FAIL is already known: `gh issue list --repo code-yeongyu/lazycodex --search "<short symptom>" --state open` (and `openai/codex` when the failure points upstream). Link matches in the report instead of re-diagnosing from scratch.
 7. If a probe fails and the cause is not explained by config or source comparison, invoke `$omo:debugging` for the investigation. If Codex exposes only unqualified skill names in the current session, invoke `$debugging` and state that it is the OMO debugging skill.
 8. Emit the report.
diff --git a/plugins/omo/test/lcx-bug-skills.test.mjs b/plugins/omo/test/lcx-bug-skills.test.mjs
index edf1783..6c4134b 100644
--- a/plugins/omo/test/lcx-bug-skills.test.mjs
+++ b/plugins/omo/test/lcx-bug-skills.test.mjs
@@ -16,3 +16,14 @@ test("#given lcx skills #when frontmatter is inspected #then each exposes the lo
 		assert.match(skill, new RegExp(`^name: ${skillName}$`, "m"), `${skillName}: frontmatter must expose ${skillName}`);
 	}
 });
+
+test("#given a non-Windows install #when lcx-doctor compares hook manifests #then Windows-only Git Bash hooks are expected materialization", async () => {
+	// given
+	const skill = await readFile(join(sharedSkillsRoot, "lcx-doctor", "SKILL.md"), "utf8");
+
+	// then
+	assert.match(skill, /removeGitBashHooksOffWindows/);
+	assert.match(skill, /pre-tool-use-recommending-git-bash-mcp\.json/);
+	assert.match(skill, /post-compact-resetting-git-bash-mcp-reminder\.json/);
+	assert.match(skill, /non-Windows.*expected materialization/i);
+});

Verification

  • RED: targeted test produced 1 pass and 1 fail before the guidance change.
  • GREEN: node --test plugins/omo/test/lcx-bug-skills.test.mjs produced 2 passes and 0 failures.
  • Adjacent: root npm test produced 9 passes and 0 failures.
  • Manual QA: a fresh codex exec $omo:lcx-doctor invocation on Linux reported Healthy, Drift verdict: PASS; all 21 materialized hooks and runtime targets passed, and LCX_PLUGIN_PROBE_OK loaded the real hooks.
  • Full plugins/omo suite is not runnable from this generated mirror snapshot: missing sibling shared-skills/plugins package artifacts cause unrelated ENOENT/module failures.

This fix was debugged, implemented, and verified with LazyCodex.
Tag: lazycodex-generated

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions