fix(doctor): validate the Cursor hooks document the manifest names (#438) - #442
Conversation
The Cursor static validator hard-coded `hooks/hooks.json` and parsed it with the Cursor hooks schema regardless of what `.cursor-plugin/plugin.json#hooks` pointed at. The unified `plugin` target emits the Claude/Codex-format document at that path and points Cursor at `hooks/hooks-cursor.json`, so `agent-bundle doctor` reported AB7320/AB6027 (and `doctor --from`, AB7319) against a byte-for-byte install of a bundle `validate` had accepted. Resolve the hooks source from the manifest the way the pinned loader does (`resolveCursorHooksSource`): a string is a plugin-root-relative file that replaces folder discovery, an object is validated inline, and an absent field falls back to `hooks/hooks.json`. A declared file that is missing or resolves outside the plugin root is an AB6027 error. Doctor's AB7322 registration proof and the `agent-bundle/test` installed-host check share the resolver, so the validator, `doctor`, `doctor --from`, and the registration proof agree on which file counts. Tests: validator positives/negatives for named, missing, escaping, and inline hooks; Doctor installed and `--from` unified-bundle cases plus the folder-discovery fallback; the host-install fixture now also builds the `plugin` bundle and the Cursor proof installs it into a fresh isolated home asserting zero AB7320/AB6027 findings and `hooks/hooks-cursor.json` registration. Closes #438
🦋 Changeset detectedLatest commit: d42986b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
To use Codex here, create an environment for this repo. |
|
@codex review |
commit: |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Fixes #438 —
agent-bundle doctorreportedAB7320: … AB6027: hooks/hooks.json/hooks: must NOT have additional properties(3×, one per hook event) against a freshly installed unifiedpluginbundle, anddoctor --from artifact/pluginreported the same underAB7319, marking the bundlecorrupt.Root cause.
validateCursorPluginFiles(src/host-contracts/cursor-plugin-validation.ts) hard-codedhooks/hooks.jsonas the Cursor hooks document and parsed it with the pinned Cursor hooks schema regardless of the.cursor-plugin/plugin.jsonhooksfield. The unifiedplugintarget (src/adapters/plugin.ts) deliberately emits the Claude/Codex-format document athooks/hooks.json(both hosts discover it there) and points the Cursor manifest athooks/hooks-cursor.json, relying on Cursor's documented "a manifest field replaces folder discovery" rule (pinned incapabilities/cursor-2026-08-28.json). The PascalCase Claude event names were the "additional properties". The standalonecursortarget emits only the Cursor-format document athooks/hooks.json, so it was unaffected; the emitter is not defective.Fix. Resolve the hooks source from the manifest the way the loader does (
resolveCursorHooksSource, exported and shared):hooks/hooks-cursor.json/…in messages); missing →AB6027error ("declares hooks at … but … is missing"); absolute or../escape →AB6027error;.cursor-plugin/plugin.json#/hooks(token rules apply to its commands, not to manifest metadata);hooks/hooks.json, optional as before.The same resolver now drives Doctor's
AB7322registration proof (cursor-hooks-registration.ts, which also gains the folder-discovery fallback) and theagent-bundle/testinstalled-host hook check (src/test/installed.ts), sovalidateCursorPlugin,doctor,doctor --from, andAB7322agree on which file counts. Claude/Codex validators keep their ownhooks/hooks.jsoncontract.Docs:
docs/diagnostics.md(AB6027,AB7320,AB7322) and the websiteinstallation.mdx/validation.mdx(en + zh). Changeset:agent-bundlepatch.Evidence
Reproduced with a
targets: ['plugin', 'portable']project carrying threeplugin-only hooks (the issue's cargo-hauler shape), built with the repo CLI, installed via the emittedinstall.mjsinto an isolatedHOME:HOME=$ISO agent-bundle doctor --host cursor3 error(s):AB7320 … AB6027: hooks/hooks.json/hooks: must NOT have additional properties×30 error(s);AB7322info registerspreToolUse, sessionStart, stop (3 command(s))agent-bundle doctor --host cursor --from artifact/plugin --jsonAB7319(same AB6027 text),bundle.state = corruptAB7319/AB7320,bundle.state = installedTDD evidence: with
packages/agent-bundle/srcstashed, 5 of the 6 new validator tests fail (named-file positive, own-path negative, missing, escaping, inline) and the extended Cursor host-install proof fails with exactly the issue'sAB7320 … AB6027: hooks/hooks.json/hooks: must NOT have additional properties.Test plan
pnpm typecheck— exit 0pnpm lint— 0 errors / 0 warnings (1161 files)pnpm test:unit— 204 files, 3016 passed, 0 failed, 5 skipped (pre-existing opt-ins)pnpm exec rstest --config rstest.unit.config.ts packages/agent-bundle/tests/cursor-plugin-validation.test.ts— 18 passed (6 new)pnpm exec rstest --config rstest.unit.config.ts packages/agent-bundle/tests/doctor.test.ts— 58 passed (3 new: installed unified bundle,--fromunified bundle, folder-discovery fallback)pnpm test:host-install:build— 19 passed, 2 skipped (Claude live-session / Codex opt-ins); real Claude and Codex installs ran; the Cursor proof now also installs thepluginbundle into a fresh isolated home and assertsunifiedBundle: { hooksDocument: 'hooks/hooks-cursor.json', hooksRegistration: 'registered', staticFindings: { AB6027: 0, AB7320: 0 } }pnpm docs:site:build— exit 0, language parity checkedd42986bea3(Verify Node 24, Release gates, Host install proofs, Examples check, RSC runtime micro-eval, Docs site, Changeset present, Dependency review)@codex reviewcites head SHAd42986bea3Review status
d42986bea3.chatgpt-codex-connector: reviewedd42986bea3twice (20:00 UTC and 20:06 UTC) — "Didn't find any major issues", zero review threads.d42986bea3(current head). Unreviewed SHAs: none.