Skip to content

fix(doctor): validate the Cursor hooks document the manifest names (#438) - #442

Merged
ScriptedAlchemy merged 2 commits into
mainfrom
fix/438-cursor-hooks-validation
Sep 3, 2026
Merged

fix(doctor): validate the Cursor hooks document the manifest names (#438)#442
ScriptedAlchemy merged 2 commits into
mainfrom
fix/438-cursor-hooks-validation

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #438agent-bundle doctor reported AB7320: … AB6027: hooks/hooks.json/hooks: must NOT have additional properties (3×, one per hook event) against a freshly installed unified plugin bundle, and doctor --from artifact/plugin reported the same under AB7319, marking the bundle corrupt.

Root cause. validateCursorPluginFiles (src/host-contracts/cursor-plugin-validation.ts) hard-coded hooks/hooks.json as the Cursor hooks document and parsed it with the pinned Cursor hooks schema regardless of the .cursor-plugin/plugin.json hooks field. The unified plugin target (src/adapters/plugin.ts) deliberately emits the Claude/Codex-format document at hooks/hooks.json (both hosts discover it there) and points the Cursor manifest at hooks/hooks-cursor.json, relying on Cursor's documented "a manifest field replaces folder discovery" rule (pinned in capabilities/cursor-2026-08-28.json). The PascalCase Claude event names were the "additional properties". The standalone cursor target emits only the Cursor-format document at hooks/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):

  • string → plugin-root-relative file, validated under its own path (hooks/hooks-cursor.json/… in messages); missing → AB6027 error ("declares hooks at … but … is missing"); absolute or ../ escape → AB6027 error;
  • object → validated inline as .cursor-plugin/plugin.json#/hooks (token rules apply to its commands, not to manifest metadata);
  • absent → folder-discovery default hooks/hooks.json, optional as before.

The same resolver now drives Doctor's AB7322 registration proof (cursor-hooks-registration.ts, which also gains the folder-discovery fallback) and the agent-bundle/test installed-host hook check (src/test/installed.ts), so validateCursorPlugin, doctor, doctor --from, and AB7322 agree on which file counts. Claude/Codex validators keep their own hooks/hooks.json contract.

Docs: docs/diagnostics.md (AB6027, AB7320, AB7322) and the website installation.mdx / validation.mdx (en + zh). Changeset: agent-bundle patch.

Evidence

Reproduced with a targets: ['plugin', 'portable'] project carrying three plugin-only hooks (the issue's cargo-hauler shape), built with the repo CLI, installed via the emitted install.mjs into an isolated HOME:

Command Before After
HOME=$ISO agent-bundle doctor --host cursor 3 error(s): AB7320 … AB6027: hooks/hooks.json/hooks: must NOT have additional properties ×3 0 error(s); AB7322 info registers preToolUse, sessionStart, stop (3 command(s))
agent-bundle doctor --host cursor --from artifact/plugin --json AB7319 (same AB6027 text), bundle.state = corrupt 0 AB7319/AB7320, bundle.state = installed

TDD evidence: with packages/agent-bundle/src stashed, 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's AB7320 … AB6027: hooks/hooks.json/hooks: must NOT have additional properties.

Test plan

  • pnpm typecheck — exit 0
  • pnpm 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, --from unified 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 the plugin bundle into a fresh isolated home and asserts unifiedBundle: { hooksDocument: 'hooks/hooks-cursor.json', hooksRegistration: 'registered', staticFindings: { AB6027: 0, AB7320: 0 } }
  • pnpm docs:site:build — exit 0, language parity checked
  • CI green on head d42986bea3 (Verify Node 24, Release gates, Host install proofs, Examples check, RSC runtime micro-eval, Docs site, Changeset present, Dependency review)
  • @codex review cites head SHA d42986bea3

Review status

  • CI: all checks green on head d42986bea3.
  • chatgpt-codex-connector: reviewed d42986bea3 twice (20:00 UTC and 20:06 UTC) — "Didn't find any major issues", zero review threads.
  • Last codex-reviewed SHA: d42986bea3 (current head). Unreviewed SHAs: none.

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-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d42986b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Patch

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

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@pkg-pr-new

pkg-pr-new Bot commented Sep 3, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@442
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@442
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@442

commit: d42986b

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: d42986bea3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-03T20:06:16.486250Z d42986b Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: d42986bea3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant