Skip to content

The plugin's hooks

The plugin’s hooks run the project’s cora, in Claude Code and Codex alike, as cora hook. Some ask you before an edit of what decides what CORA reports. The others keep CORA’s tools in the fixer, start it after UI edits and keep it going.

The hooks need:

  • The project’s cora. Each hook runs the project’s node_modules/.bin/cora hook. The hooks do nothing in a project without @vispero/cora, or with a release too old to have cora hook. Nor do they in a checkout without its dependencies installed, such as a new git worktree. Nor do they in a session started in a package of an npm or Yarn workspace, whose bin the package manager usually links at the workspace root, or on Yarn’s Plug’n’Play, which links none.
  • A supported Node.js (Requirements), as the node that PATH finds. On an older release, the hook says so as a warning, and does nothing else.
  • The project root. Claude Code runs each hook in the directory you started Claude Code in, wherever the agent has moved since. Codex runs it in the session’s directory, so start Codex from the project root, where node_modules is.
  • A CORA account. Without one, the first hook of a session says why, as a warning, once. The hooks then do nothing else for the rest of the session, but those that ask before an edit. Your account token may be signed with a key the release does not hold. The hooks then wait a few seconds at most for the service’s current keys, then say why the same way.
  • On Windows, Git Bash for Claude Code. Claude Code runs the hooks in Git Bash, which Git for Windows installs. Without it, Claude Code shows a hook error each time a hook runs, and the hooks do nothing. Codex runs them in PowerShell.

Before an agent edits one of these, the hook asks you whether the edit goes ahead, and says why:

  • A baseline, any file named cora.baseline.json. A baseline accepts the issues it holds, so the gate stops reporting them.
  • CORA’s config, any file named cora.config.json or cora.config.ts. Its rule settings choose which rules CORA runs.
  • A comment that turns accessibility checks off or down. It hides what the check finds, or keeps it from failing, without fixing it. These count:
    • an eslint-disable, eslint-disable-line or eslint-disable-next-line comment that names a rule of eslint-plugin-jsx-a11y, eslint-plugin-vuejs-accessibility or eslint-plugin-lit-a11y, eslint-plugin-svelte’s svelte/valid-compile, or a rule of angular-eslint’s template accessibility config;
    • such a comment that names no rule, in a file of markup, components or styles;
    • an eslint comment that sets such a rule to off or warn, such as /* eslint jsx-a11y/alt-text: off */;
    • a Svelte svelte-ignore comment that names an accessibility warning.
  • Your user config. It holds your own settings, which CORA takes in every project.

The hook asks whichever agent makes the edit, CORA’s fixer included. It decides from the edit alone, so it needs no account. It asks even when the other hooks can do nothing, such as when the project’s config cannot load.

  • Claude Code asks you in every permission mode. In a run with no one to answer, such as claude -p, it refuses the edit and tells the agent why.
  • An edit an agent makes through a shell command, such as a redirect or sed, reaches no hook in either agent, so nothing asks about it.
  • A baseline you name with --baseline <path> under another name is not covered.
  • A comment the edit only keeps, such as on a line it rewrites, asks nothing. Nor does one it narrows, such as an eslint-disable turned into an eslint-disable-next-line, a rule’s off turned down to warn, or an eslint-disable that names no rule narrowed to one rule. One it widens asks.
  • Claude Code’s Write replaces a whole file without naming what it held, so it asks for every such comment in the file it writes.
  • In Claude Code, an edit inside a comment over several lines asks nothing when it leaves out the comment’s first line. Setting a rule to off inside an existing /* eslint */ comment can do that. Claude Code gives the hook only the text the edit replaces.

CORA’s tools run in its fixer. When any other agent calls one of them, the main one included, the hook turns the call back. It tells the agent to dispatch the fixer instead:

  • in Claude Code, cora:a11y-fixer, or cora-a11y-fixer in a project that holds the project agent;
  • in Codex, a11y_fixer, spawned with fork_turns set to "none".

The fixer does not see your conversation, so the agent hands it a brief that stands alone. The brief holds:

  • your request as you wrote it;
  • the pages, components or files it names or implies;
  • anything you decided or ruled out, such as report only or no edits.

The fixer carries the request out and returns a summary.

In Codex, the hook does this only where the project has CORA’s fixer agent for Codex: an agent named a11y_fixer that one of these gives Codex (In Codex):

  • the project’s .codex/config.toml, declaring an agent file that exists, as cora init declares it, with a description in the file or the declaration;
  • an agent file of that name in the project’s .codex/agents/, with a description and developer_instructions, as Codex requires;
  • your Codex home, in either of those ways.

Without one, the agent that calls CORA’s tools runs them, and the hook tells you once a session to run cora init --agent codex.

In Codex, the hook also turns back a spawn of a11y_fixer that would hand it your conversation’s history, which Codex does unless the spawn sets fork_turns to "none". It tells the agent to spawn the fixer again that way, with its brief in its message. The hook knows Codex’s spawn tool by its name under Codex’s default namespace for agent tools, collaboration. With multi_agent_v2.tool_namespace set to another namespace in your Codex config, it does not see the spawn.

Once an agent other than the fixer edits your app’s UI, the hooks have the main agent dispatch the fixer before it finishes. They then keep the fixer going on the issues it attempted.

The hooks record an edit of a UI file inside the project:

Kind Extensions
Markup .html, .htm, .mdx
Components .jsx, .tsx, .vue, .svelte, .astro
Styles .css, .scss, .sass, .less

They record no other file, such as a .js or .ts file, no test file, such as Form.test.tsx, Form.spec.tsx, Cypress’s Form.cy.tsx or one under __tests__/, which renders on no page, and no edit made through a shell command. They record the fixer’s own edits only to hold its result to those files, since it checks them itself. Then:

  • When the main agent finishes its turn with edits recorded, the hook asks it to dispatch the fixer, to scan the pages that render those files and fix what it finds. Its brief quotes what you asked about accessibility, if anything, so the fixer fixes the issues your baseline accepts only when you asked for them. It asks once a turn, and again at your next turn until a fixer takes the edits.
  • Claude Code shows the ask as hook feedback, not as a hook error. In Codex, in a project without the fixer agent, the hook does not ask, as Scans run in the fixer says.
  • The fixer is handed the edited files as it starts. It includes the pages that render them in its scan where your request’s scope takes them in. A request that names its pages leaves the others unchecked, and the fixer names them in its answer.
  • The main agent can finish before the fixer does, since the fixer runs in the background in Claude Code by default and in Codex. To have Claude Code’s main agent wait for the fixer, set CLAUDE_CODE_FORK_SUBAGENT to "0", which turns fork mode off for the whole session, as In Claude Code describes with its cost. When the main agent finishes a turn with edits recorded while that fixer still runs, the hook hands the edits to the fixer, which it tells of them at its next call of CORA’s tools, checking their pages whatever pages your request named, but for what you ruled out, and asks the main agent to leave the files alone until the fixer’s result arrives, rather than dispatch a second fixer on the same files. In Claude Code the fixer runs while Claude Code lists it among the session’s background tasks; where no list comes, as in Codex, it runs while it has called CORA’s tools or edited a file within the last 10 minutes. A fixer that ended without finishing, such as one you cancelled, hands every file it held back to the main agent’s next check.
  • When the fixer finishes while an issue it attempted still has attempts left, and verify_fix confirmed no fix of it, the hook keeps the fixer on that issue. It does so for as long as the fixer keeps attempting it, until the issue is fixed or handed to you, then has it summarize.
  • At each of the fixer’s calls of CORA’s tools, the hook records a digest of the content of the files it knows of: those it was handed, those it edited and the edits it was told of. When the fixer finishes while a file’s content differs from what the newest scan or verify_fix of each page it scanned saw, the hook keeps it going: it verifies its own fixes in those files, scans their pages again, works what it finds, a fix of its own that the change undid among it, and starts its answer with every result of its run, since only that answer reaches the main agent. It goes by content, so a change made through a shell command, a deleted file and a copy that keeps an old modification time count, and a file rewritten unchanged does not. With no map from files to pages, a change after any page’s newest scan counts, so the fixer may scan once more than it needs. It asks again only about content it has not asked about since it last verified other content. A file the fixer holds that no scan since it started saw as it is, such as one it was asked about and never scanned again, goes back among the recorded edits when the fixer finishes, so the main agent is asked for a fixer for it, unless the hook cannot read it and already asked the fixer about it so.
  • In Claude Code’s auto mode, the fixer hands its report back through a tool whose call ends its run, so the same check runs before that call: the hook refuses the hand-back with what to do, at most twice, and the third always goes through.
  • Nothing waits on the outcome: what the fixer leaves is reported, and what to do about it stays your decision.

The hooks do not run in Codex before you trust them, under Claude Code’s disableAllHooks setting, or in a checkout without node_modules. There, the plugin’s cora:a11y-check-ui-edits skill has the main agent ask for the same check itself: see The check after UI edits. Pausing CORA stops both for a session.