Guide
How it works
- The widget is served by a small local server that the plugin starts
when you run
/annotant, for the session’s life. No server, no widget: nothing runs when Claude is not watching. - Your project only gets a few lines: a dev-only loader snippet, a
.annotant.jsonconfig, and a.annotant/folder for the comments (git-ignored by default;comments.json, its format versioned, each thread whole with its state and history: see the comments file). The widget’s code never enters your project or your production builds;initproves it by scanning a production build. - The widget is framework-agnostic. It ships its own React inside a shadow root, so it works on Next.js, Vite, CRA or anything served by a dev server, without touching your styles or dependencies.
- No session waits on another. The plugin’s hooks read the server and start each comment’s agent at once, in the background, with no model turn: keep working with Claude in the same session, comments are not held up by it. Three agents run at a time; a fourth comment starts when one ends.
- Agents work in parallel safely. One background agent per comment; its own hooks take a per-file lock before every write, so two agents never edit the same file at once, and refuse it a Bash command that writes files, which no lock could cover. What each agent does (files read and edited, commands, MCP calls, its messages and thinking) shows up live under the comment. The plugin’s hooks are a Claude Code mod, loaded once per session, for the watch; each comment’s agent runs a short script around each of its tools, and no other agent does.
- Each comment gets a model that fits it. Before its agent starts, a
classifier sorts the comment as
trivial,standardorhard, andmodelsin.annotant.jsonpicks the model for it (by default Haiku, the session’s own model, and Opus). The widget names it on the thread (“haiku · trivial”). A comment of images alone is not classified: it starts on the session’s model at a medium effort. See Models.
Install
From nothing to a first comment on your app, in four steps.
1. Check the requirements
- Claude Code 2.1.287 or later, for its mods:
claude --version, andclaude updatewhen it is older. On an older Claude Code,/annotant:annotantsays so and does not start, and/annotantdoes not exist. - Node.js 22 or later:
node --version. - A JavaScript web frontend served by a dev server: a
package.jsonand Next.js, Vite, CRA, Astro, SvelteKit, Angular or another framework (or anindex.htmlat its root).
2. Install the plugin
Straight from GitHub, no clone needed:
claude plugin marketplace add annotant/annotant#stable
claude plugin install annotant@annotant
Or, inside a session: /plugin marketplace add annotant/annotant#stable,
then /plugin install annotant@annotant.
3. Set it up in your project
Start Claude Code in your frontend project, and run:
/annotant:annotant init
That is the skill’s full name; see Commands.
init detects the framework, the dev URL, the check commands (type-check,
lint) and the folders agents may edit. It asks, in one round, only what it
could not detect, and whether to git-ignore the comments (recommended: they
hold excerpts of your pages’ HTML). Then it writes:
.annotant.json, the project’s config;- a dev-only loader snippet in the app’s client entry
(
instrumentation-client.ts,pages/_app,index.html,src/index…); .annotant/, the comments’ folder, in.gitignoreif you agreed;- a dev-only change to the Content Security Policy, proposed only when it finds one in the Next config, the HTML entry or the middleware or proxy files.
Last, it builds the app for production and checks the widget is not in it (or says so when the build fails for another reason), then lists the files it changed.
When the frontend lives in a folder of the repository, name it after the
command: /annotant:annotant init web installs Annotant in web/. The
folder is resolved from the current directory, else from the repository root.
On a monorepo root that is not itself an app, init asks which of its apps
to use.
4. Start commenting
/annotant
(/annotant web for a folder.) Then open your dev app in the browser, its
dev server running as usual: a small strip of icons shows in the bottom right
corner of the page. Click the Comment icon (hover one for its name),
write what to change and send it: an agent starts, its progress shows live
under your comment, and it answers in the same thread. In the widget tells the rest.
No icons? See the widget does not show.
Commands
| Command | What it does |
|---|---|
/annotant:annotant init [dir] |
Install in this project, or its folder dir (asks only what it cannot detect) |
/annotant [dir] |
Watch the app: open your dev app and comment |
/annotant stop |
Stop watching |
/annotant status |
The server, the agents at work and the comments waiting |
/annotant:annotant update |
Migrate an older install |
/annotant:annotant uninstall |
Remove Annotant from the project |
Plugin skills are prefixed with the plugin name, hence /annotant:annotant;
/annotant is a command of the plugin’s hooks, and runs at once, even while
Claude is answering. /annotant:annotant with no mode watches too, through
the skill.
While it watches
Work on in the same session: the comments go straight to their agents, never
through your conversation. When an agent ends, its one-line answer shows in
the session’s transcript (Claude does not read it), and a question it asks in
the thread shows as a notification too. Closing the session stops the server;
/annotant starts it again, and nothing is lost.
In a git worktree, both commands find the project in the main checkout when
the worktree lacks .annotant.json.
Updating
The stable branch holds the released versions; main is where development
happens and does not work without a build. Get new versions with claude
plugin marketplace update annotant, or turn on auto-update for the
marketplace in /plugin; the changelog lists what each brings.
After an update, /annotant:annotant update migrates a project installed
by an older version.
Coming from Code Buddy, Annotant’s former name: remove the old plugin and
its marketplace (claude plugin uninstall code-buddy@code-buddy, then
claude plugin marketplace remove code-buddy), install Annotant as in
step 2, and run /annotant:annotant update in each
project: it moves .code-buddy.json and .code-buddy/ to .annotant.json
and .annotant/, and updates the loader and .gitignore.
Uninstalling
/annotant:annotant uninstall removes the loader snippet (and a file
init created only for it), .annotant.json and the .gitignore entry. It
asks before deleting .annotant/, which holds your comments. A dev-only
change init made to the Content Security Policy stays: revert it by hand.
Remove the plugin itself with claude plugin uninstall annotant@annotant,
or from /plugin.
In the widget
While the panel is closed, the tools sit in a small strip of icons in the bottom right corner, their names and shortcuts in the tooltips; a tool in progress shows its Cancel there.
- Comment opens a new comment for the page, or the whole app.
- Select text, then Comment, to anchor a comment to that text. A bubble then marks the text on the page; clicking it opens the comment’s thread.
- Point at element starts a comment with that element in its message box, as an image would be: the comment is about the page, and the element goes with its message. It stays outlined while you write, then a bubble marks it until the thread is first resolved. A reply that picks an element afterwards moves the mark to it. Hovering an element chip outlines it on the page, clicking it scrolls to it (navigating first if it lives on another page).
- Draw area to frame a part of the page no single element holds: the gap between two blocks, a misaligned group, empty space. Drag a rectangle (scroll the page while dragging to stretch it past the window); the elements it covers are outlined as it grows. A page-level comment can get one too, with Draw an area. Claude receives its box, the window it was drawn in, the element it lies in and the elements it covers or cuts through. The area is found again in that element, on another window size or after the layout changes.
- Draw freehand over the page when the feedback is easier to draw than to write: an arrow from a block to where it should go, a circle around a detail, a line along an alignment that is off. Draw with the mouse, a pen or a finger; Undo takes the last line back, Clear all of them, Done (or Enter) ends the drawing. A page-level comment can get one too, with Draw on the page. The drawing stays on the page while you write, and is placed as an area is: in the element its lines lie in, stretching with its width. Claude receives the lines, the elements around them (a margin past their box, so what an arrow points at is in) and the one the longest line points at (under its end, or the next one along it when it ends in a gap), and a picture of the lines over the page, close to what you saw: its colours, text and same-origin images where they are.
- On the page, blue is what you are pointing at or commenting on; a rainbow, around an element or an area, on a drawing’s lines or behind a text, marks a comment Claude has yet to resolve; once its thread is first resolved, the mark goes away.
- Paste or drop an image (a screenshot, a mockup) in the message box of a comment or a reply: it goes with the message, or alone, and Claude opens it. Up to 4 per message, PNG, JPEG, GIF or WebP, 5 MB each.
- Pick an element in the page from any message box, a new comment’s or a reply’s: its pointer button picks one with the panel still open (“this one too”). One per message: picking another replaces it. Its chip in the thread scrolls to it.
- All comments lists every discussion, newest first, filtered by status.
- While Claude works: what the agent is doing now (“Reading
App.tsx…”, “Thinking…”), its latest steps (files read and edited, commands, searches, MCP calls, failed tools in red, its messages and its thinking), and Cancel. Claude Code often hides the thinking itself; its summary shows when available, for instance with"showThinkingSummaries": truein your Claude Code settings. Cancelling stops the agent and lists the files it had already changed; edit the comment and send it again to hand it to a new agent. - Once answered: reply in the thread to follow up, reusing the same agent and its context when it is still around.
- Keyboard shortcuts, ⌘⇧ on macOS and Ctrl+Shift elsewhere, wherever the
focus is but in a field of your app:
,comments on the selected text (or on the page),.points at an element (while you write a comment or a reply, picks one for it),Enteropens or closes the panel,↓and↑go to the next and previous comment on the page (not from a message box, where they select text). While pointing, out of a text box, the arrow keys move the outline to the parent, the first child or a sibling, andEnterpicks it.?in the panel lists them, named as your keyboard types them in Chromium (;for,on AZERTY).
Troubleshooting
The widget does not show
When the dev app shows no strip of icons in its bottom right corner:
- Run
/annotant status: the server must be running, at the address in the loader snippet (http://127.0.0.1:<port>); the snippet keeps the portinitwrote, so a newportin.annotant.json(orANNOTANT_PORT) needs it changed too. - No
/annotantcommand at all: the plugin’s hooks did not load. Claude Code is older than 2.1.287 (claude update, then a new session), or mods are turned off for this session. - Reload the page once the watch has started: the loader asks for the widget
on page load. While the server is not running, the browser’s console shows
one failed request to
widget.jsper page load. - Open the dev build: the loader is removed from production builds, by design.
- A Content Security Policy must allow, in development,
script-srcandconnect-srchttp://127.0.0.1:<port>(andfont-src data:for the widget’s fonts).initproposes a dev-only change for a policy in the Next config, the HTML entry or the middleware; one set elsewhere (a server, a proxy) needs it by hand. - An app opened on a host that is not local (
localhost,127.0.0.1,[::1],*.localhost) needs its origin indevOriginsin.annotant.json.
A comment waits, or an agent seems stuck
When a comment seems to wait for nothing, run /annotant status. For each
project it watches, it tells the server’s state and address (or why it
stopped), the agents at work (their comment, agent, model and for how long),
the comments waiting for a free agent in order, and the server’s last error
line. The status line counts the comments waiting too.
When an agent shows no progress under its comment, edits a file another agent is changing, or seems stuck, turn on the hook log. It writes one line for each decision the plugin’s hooks make about the agents working on comments.
In any Claude Code session:
/annotant-debug on
It is on in every session within 5 seconds, with no restart. Then follow it from a terminal:
tail -f ~/.cache/annotant/hook.log
or run /annotant-debug (no word) to see its last 20 lines. Turn it off
with /annotant-debug off; the log stays where it is. Without the command,
touch ~/.cache/annotant/debug turns it on and removing that file turns it
off. The log keeps its last 128 to 256 KB.
Each line reads <time> <agent> <comment> <what happened>; the agent is
main for your own session, and the comment - when there is none:
… main - hooks loaded in a new session (Claude Code 2.1.287)
… a53927e6… e329bcbb… started on claude-haiku-5-5, the comment classified trivial
… a53927e6… e329bcbb… started for NEW
… a53927e6… e329bcbb… locked api/presets.py
… a53927e6… e329bcbb… recorded edit done "../api/presets.py"
… a53927e6… e329bcbb… run ended (answered or asked): released 1 lock(s)
| Line | What it means |
|---|---|
hooks loaded in a new session |
The hooks run in that session. No line at all after starting a session: they did not load (Annotant needs Claude Code 2.1.287 or later). |
locked <file> |
The agent holds that file, named from the repository root, until its run ends. |
waiting for <file>, held by comment <id> |
Another comment’s agent is changing it; the agent waits up to 6 seconds. |
refused <file>: still held by comment <id> |
The wait ran out: the agent releases its locks, re-reads and retries. |
no lock for <file>: outside <repository> |
Files outside the git repository take no lock. |
refused a Bash write: <command> |
The agent tried to write files from Bash, where they cannot be locked; it is told to use Edit or Write. |
recorded <step> |
A step shown under the comment, once the agent claimed it, until its run ends or stops. |
run ended …, agent stopped … |
The agent answered, asked, or stopped: its locks are released. |
turn ended: <its last line> |
The agent ended its turn: the next comment in line may start. |
hook.mjs failed: <error> |
The agent’s hook could not run: its tool ran without a lock or a step. |
started on <model>, … |
The model the comment’s agent runs on, its effort when routing set one (at medium effort), and how the classifier sorted the comment. |
started for NEW, sent the follow-up |
/annotant started the comment’s agent, or passed the reader’s reply or edit to the one already started. |
the comment still waits: sent the reply again |
The reader answered as the agent ended its turn, unread: it goes to the agent again. |
turn ended with the comment working: … |
The agent ended its turn neither answering nor asking: the comment stops, and the reader sends it again from its thread. |
could not start an agent: <why> |
Claude Code refused the agent (background tasks turned off, say); a notification says so too. |
stopped: the reader ended the comment |
The reader stopped, resolved or deleted the comment while its agent worked. |
Security
- The server listens on
127.0.0.1only and accepts cross-origin requests fromlocalhostorigins only (anddevOrigins); it refuses any change from another origin (403). - The loader is guarded by the bundler’s compile-time dev flag
(
process.env.NODE_ENVorimport.meta.env.DEV), so it is removed from production builds;scripts/verify-prod.mjschecks the build output. - Comments hold page text and HTML excerpts; they stay in your project folder,
with the images attached to them (
.annotant/images/), which the server checks are images by their bytes.
To report a vulnerability, see the security policy.