oh-my-claudeagent / hooks
AGENTS.md3 starsChanged 32 days agoMITView on GitHub
hooks/hooks.json is the canonical hook registry. Query it directly for counts. Registered here: SessionStart, UserPromptSubmit, UserPromptExpansion, SubagentStart, PreToolUse, PostToolUse, PostToolUseFailure, Stop, TaskCompleted. Regenerate that list with jq -r '.hooks | keys[]' hooks/hooks.json. Every other platform event is unregistered on purpose; docs/references.md carries the per-event reason. register.ts is the only file that calls on(). It gives each event one dispatcher from dispatch.ts, which runs the features that share the event in a fixed order, each in its own try/catch. host.ts defines…
- Deletes or force-pushes
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,774 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository22 other files it gives its agents.
- AGENTS.mdAGENTS.md
- opencode/AGENTS.mdAGENTS.md
- scripts/AGENTS.mdAGENTS.md
- servers/AGENTS.mdAGENTS.md
- skills/AGENTS.mdAGENTS.md
- statusline/AGENTS.mdAGENTS.md
- templates/AGENTS.mdAGENTS.md
- tests/AGENTS.mdAGENTS.md
- analyzerSkill
- build-fixerSkill
- consolidate-memorySkill
- debuggingSkill
- github-triageSkill
- git-masterSkill
- handoffSkill
- init-deepSkill
- omca-setupSkill
- planSkill
- refactorSkill
- remove-ai-slopsSkill
- reviewerSkill
- start-workSkill
The file
We split this AGENTS.md into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 202
No OMCA hook returns an allow on any event: hooks deny, ask or steer, and allow decisions come from the permission rules in the user's …
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Hook inventory
hooks/hooks.json is the canonical hook registry. Query it directly for counts.
Hook events
Registered here: SessionStart, UserPromptSubmit, UserPromptExpansion,
SubagentStart, PreToolUse, PostToolUse, PostToolUseFailure, Stop,
TaskCompleted.
Regenerate that list with jq -r '.hooks | keys[]' hooks/hooks.json. Every other platform
event is unregistered on purpose; docs/references.md carries the per-event reason.
The mod
register.ts is the only file that calls on(). It gives each event one dispatcher from
dispatch.ts, which runs the features that share the event in a fixed order, each in its own
try/catch. host.ts defines the Host closures a feature receives in place of $, so a
feature module takes host and never $. The features are bash-guard, server-check,
mod-marker, compact, agents-tracker, agent-rows, metrics, band, pane with its tabs/,
footer, group-rows, plan-receipts, keyword-paint, spinner, completion, refusal-recovery and omca-router. doctor.ts runs the Doctor page's checks and fixes for
tabs/doctor.ts; it registers no event. Pure logic lives in src/core/.
spinner rewrites only the suffix of the terminal's main-loop Spinner, to … · task 7/14, from
the band atom. A Spinner whose requestId is a tracked agent id, and every desktop Spinner, pass
through unchanged.
Three features redraw Claude Code's own transcript rows on the terminal, and every other surface keeps the engine's rows. None of them reads a file during a drawing, and the row hooks read only atoms written once per turn or per finished agent, since a row subscribes to every atom it reads.
footerkeeps each main turn's tokens, cost, verification receipt and the agents still running in theturnsatom atturn.complete, and draws them in theTurnDurationline. A line belongs to the main turn that started last before the line was first drawn (turn.starttimes, kept in the module), so a turn that ends before the previous turn's facts land still draws its own; a line first drawn before any start, as after /resume, and a line whose turn has no record yet keep the engine's. The line names the agents the engine lists inwaitingFor. The engine keeps its own line, with the facts on a row beneath, in the ctrl+o view (isExpanded), while the turn waits on a workflow, and while agents run that the props do not name, as from a client older thanwaitingFor. A session without a terminal gets the facts as theturn.completetext instead.agent-rowsredraws theToolUserow of a single Agent call from its own input (registered withprops: { tool: "Agent" }, so no other tool row reaches the mod); several agents launched together are one engine block no hook reaches. It also keeps a finished subagent's type, task, status, duration, call count, tokens and the first line of its reply in thenoticesatom at the agent'sturn.complete, afteragents-trackerhas closed its row, and redraws theUserMessagerow of origintask-notificationwhosetask.idit holds.isExpanded(ctrl+o) keeps the engine's rows.group-rowsappends rows under a foldedToolGroup, read from its calls' own output: one per verification call (isVerificationCommand), and for a Read that returned a PNG a name row and anImageof the picture (the first three per group; the rest keep the name row). A picture whose bytes do not open as a PNG keeps its name row, since one badImagerefuses the whole tree. The previews share one tree's 2 MiB of picture bytes, the first first, and one that no longer fits is passed as its file. After the first picture of each kind it draws, bytes or a file, the feature blits it once: a refusal that names the alt means the terminal shows that kind only as text, and every later preview of that kind is the name row alone. tmux and iTerm2 3.5 and later draw bytes and not a file, and a refusal carryingfix: "tmux-passthrough"writes one transcript line naming tmux'sallow-passthroughandfocus-events. An expanded group is left as drawn.
plan-receipts adds OMCA's own rows to the transcript with $.ui.place, which the engine draws
through the Placed component on every surface. At each main turn.complete it compares the bound
plan's done tasks with those of the last main turn, and the tasks that finished get one row with
their verdicts from proofFacts, plus the plan's outcome when they end it. A row lands under the turn
line, since the engine draws that line before turn.complete hooks run, and a subagent's turn
places none because its row would land among the main turn's tool rows. The first read of a plan, at
session.start or when it becomes bound, only records what is done, and so does the first main turn
after a hot reload, which starts the module's memory over. The receipts atom holds the rows' facts,
capped at 200; a drawing reads it. Each main turn moves a stored verdict only toward proof (settled):
a run that lands late settles a row, while a later task's edit to a shared file, which makes the
live verdict read unproven, leaves it alone, and the band and the Plan tab carry the live verdict.
A turn that settles nothing writes nothing, because a write redraws every placed row. A rejected call
(a client older than 2.1.314) or a result with no requestId means no row stands, and a plan's end
then toasts; a result with a requestId stands for a surface yet to attach. The band keeps the
plan's end notification.
keyword-paint underlines what the server's keyword-detector would match in the draft, from the
same keywordSpans reader in src/core/keywords.ts, only with enableKeywordTriggers on and that
detector's switch off. A prompt.edit answer carries the runs as typing goes on, and prompt.change
repaints with $.prompt.read and $.prompt.paint for a write no edit carries, as a recalled entry
or an undo. It notes a mode at prompt.submit as the detector announces it, once a session, and
stops marking it. $.prompt.paint rejects on an old client and leaves the draft as it stands.
server-check also keeps a $.ui.status line while the omca server is not connected, which the
engine draws under the prompt as ⚠ oh-my-claudeagent: .... Each main turn.start asks the engine
again, since a server can die after it connected and no event says so. It asks from a $.clock.after
timer and one ask at a time, so a server that hangs while it connects holds up no turn. The answer
is the session's state, and a failed server stays failed until /mcp reconnects it.
agents-tracker also owns the agent page that tabs/agent-page.ts draws on the Agents tab: a
Button keyed open-<agent id> on each lane opens it, and b, c, r and v go back, copy the
brief, reload and put the agent's transcript in the main view (v reads "Back to main" while that
agent is in view). Its one tool.call registration matches { agentId: /./ }, so only a subagent's
call reaches the mod and a main-loop call never does. It appends the call and its outcome to a
module-level map, with no state write. The pages atom is written only when a page is opened, on
r and at the agent's turn.complete, from $.session.messages({ agentId }) and that map; a
denied read keeps the stored prompt. A page holds at most 8 KiB of brief, 120 calls and 4 KiB of
reply, for the newest 20 agents.
An agent.spawn carrying resume whose agent already has a row reopens that row and lane instead
of building new ones: the row goes back to running with no end time and keeps its start, tokens and
cost, the lane keeps its prompt and call count and clears its tool, output and result, and a
module-level map holds the row's tokens so the next completion adds its usage to them. metrics
rewrites that agent's record as running with the same tokens.
mascot-player animates the mascots of src/core/mascots.ts: the 15 by 4 mini that leads each
relaxed lane on the Agents tab, drawn when the body holds every running lane, a row after each, the
Finished label and the one-line rows (the latest wave's finished agents keep their lanes and still
minis while rows remain), and the 16 by 8 mascot in the agent page header. The compact
lanes and the ASCII tier draw none. show records the size each drawing laid out, so a blit
matches the mounted Raster. On a terminal the pane's tick
and its open paths call ensure, which starts one $.clock.every(FRAME_MS) timer while a laid-out
agent works. Each tick advances a module-level frame counter and awaits a $.ui.blit of each shown
working mascot, and a denied blit is a mascot that is not mounted. The render hooks pass frame() to
kit.mascot, so a redraw lands on the frame the blits are at. The timer ends when no shown agent
works, the pane closes or the surfaces lose terminal; a stop from the timer's own tick redraws the
pane once, so a blit in flight never outlasts the still frame. It writes no atom and a ui.render hook
never starts it. No timer runs on a remote surface: Desktop draws a still Svg, since it rebuilds
every site on each state write and would restart an animation, and the other remote surfaces
animate the Svg by themselves.
The UI kit (ui.ts) always sends the typeset props. Its Text sends weight: "semibold" in
place of bold: true, and on a remote surface Run sets mono on its container and every span,
a Text outside Run that holds a digit sets tabular, and the row gaps of the pane and its
tabs and the band's button gap are the named steps "md" and "lg" in place of cells. The
terminal keeps its cell gaps. resolveSession (host.ts) does not read the engine version.
The pane's ui.scroll hook reads one committed layout (regions.ts). Each terminal drawing
collects its own on view.layout: every scroll region and every list that draws its own window,
with its rect in the tab's rows and columns, the tab rows above the tab, the engine's offset it
drew at, whether the pane padded the tree, and the rows the tree takes. The drawing commits it in
one assignment when it finishes, and one that started earlier never replaces a newer commit,
because the engine raises ui.render and ui.scroll concurrently and a drawing awaits its tab.
Region offsets are module state that outlives drawings. A wheel tick, the one event that carries
pointer, moves only what is under it: a region by by rows, else a list by by rows, each
clamped and taking the tick at its end. Over nothing it reaches the engine only while the tree is
taller than the body by more than the padding row, and is taken with no effect otherwise. Routing
never reads the size of by, since the engine merges ticks in flight into one move. The scroll
keys carry no pointer and keep their own routing: the tab's list, then the aimed or first
overflowing region, a row, a page (bodyRows) or an end (contentRows) at a time.
Current runtime contract
refusal-recoveryhandles only interactive, user-origin main turns whose structuredturn.completerefusal category isreasoning_extraction, which reaches the mod only after Claude Code's own once-only retry was refused too. It queues one plugin-authored follow-up after completion asking for conclusions, evidence and actions in place of reasoning, preserves the refusal result, and does not recover the follow-up's own turn. A new user prompt or edit cancels a follow-up before it is queued; competing turns after queueing are not recovered. Every other category (cyber,bio,frontier_llm) is left to the person, andOMCA_DISABLED_HOOKS=refusal-recoveryturns the feature off. The engine validator allowsprompt.submitonly through the Host binding inregister.tsand this feature;model.fork,http.fetchandstore.*remain forbidden.UserPromptSubmitandUserPromptExpansionroute to the server'skeyword-detectorandslash-mode-detectorhandlers, and both to itsguidancehandler, which injects the guidance template, the session id and the bound plan's context on a session's first prompt.- The mod and the server each report when the other is missing. At
session.startand each mainturn.startthe mod'sserver-check.tsasks the engine to connect theomcaserver and, when it finds it not connected, writes one line to the transcript: that bun is not on PATH, or the engine's reason with a pointer to/mcp. On the launch session's first prompt, the server'smod-noticehandler answers with asystemMessagewhen the mod has written no marker for the session. A session that/clearstarts is marked only at its firstturn.start, after the prompt hook, so the handler never judges it.OMCA_DISABLED_HOOKS=mod-noticesilences the server's line. SessionStartforclearorcompactroutes to the server'ssession-starthandler, which re-injects that context after a compaction and hands it back to the next prompt after/clear. The mod'ssession.compactfeature (compact.ts) tells the summarizer to keep the bound plan with its open and blocked tasks. It does not run for a user-installed plugin where an organization manages Claude Code, so the server handler's re-injection is the only cover there.SubagentStartroutes to the server'ssubagent-contexthandler.Stoproutes to the server'sstop-gateshandler, which runs plan continuation, final verification, and the drift guard in that order and answers with the first block.OMCA_DISABLED_HOOKS=stop-gatesturns off all three;plan-continuation,final-verification, ordrift-guardturns off one.The destructive-command guard lives in the mod, on
tool.checkfor Bash, PowerShell and Monitor (whose commands are bash: Monitor needs Git Bash on Windows) (hooks/bash-guard.ts, with its patterns insrc/core/destructive.ts). A recursive removal whose target is the filesystem root, home, the working directory, or a directory directly under root or home is denied outright. The destructive git family (hard reset, stash, clean, a restore that writes the working tree, recursivegit rm, path checkout), a force push to the default branch andgit commit --no-verifyare held for review and denied wherever no dialog can show. Any other recursive removal, anxargs rm -rfwhose targets are not known, and a force push to another branch are held for review and run where no dialog can show. Where a dialog can show and theguardModeoption isdialog, the guard asks the user, and only "Run it" lets the call continue. WithguardModeset todeny, no dialog shows. The default branch is the oneorigin/HEADnames, ormainandmasterwhen it names none, read from local refs.OMCA_DISABLED_HOOKS=bash-guardturns the review off and never the outright deny.The patterns match at any command position: string start, after a separator, or inside a subshell or command substitution, behind wrappers such as
sudo,env,command,timeoutandxargsandVAR=valueprefixes.rmoptions and the options ofgit reset,git rm,git restoreandgit commitcount wherever they stand before--. The guard also reads commands handed to another interpreter:bash -c,sh -c,eval, a heredoc fed to a shell,pwsh -Command,cmd /candInvoke-Expressionwith a literal string. Anchoring on command position keeps a literal mention out of scope, since thermingrep -rn "rm -rf" scripts/follows a quote rather than a separator.tool.checkruns before every Bash, PowerShell and Monitor call in every permission mode, so the guard never depends on a dialog being shown. The platform's critical-path check onrmandrmdirasks in the terminal or denies in auto mode, and the classifier does not review those removals. That check covers critical paths only, while the guard also covers the destructive git family, force pushes and other recursive removals, so it must not be deleted as duplicated platform behavior.tool.checknever returns an allow, because an allow there skips the auto-mode classifier. No OMCA hook returns an allow on any event: hooks deny, ask or steer, and allow decisions come from the permission rules in the user's settings. Nothing is registered onPermissionRequest. That event fires for a call that cannot prompt, such as-poutsidedontAsk, which the platform would otherwise auto-deny, so a hook allow there would run a command the platform refuses. To cut prompts for a tool the user trusts, point them to an allow rule in settings or to/fewer-permission-prompts.- Claude Code owns the hook lifecycle. OMCA supplies the mod's module and the
type: mcp_toolhandlers whosetoolisomca_hook, and every registered handler is of that type. Start-up pruning and the exit unbind run in the server process, outside any hook.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

