This page applies to the current IDE & CLI.

Hooks

Hooks let you observe, control, and extend the agent loop. CFactory supports two complementary surfaces that share one lifecycle:

  1. Declarative hooks — Cursor-compatible hooks.json + command/prompt scripts (stdin/stdout JSON)
  2. TypeScript plugins — the existing @cfactory/plugin Hooks API

Both see the same agent events in the same order.

What you can do

  • Run formatters after edits
  • Gate risky shell or MCP calls (allow / deny / ask)
  • Block sensitive file reads
  • Validate prompts before submit
  • Inject session context or env at session start
  • Follow up when the agent stops (stop / subagentStop)

Configuration

SourcePathScript cwd
Managed (MDM)/Library/Application Support/cfactory/hooks.json (or platform equivalent)managed config dir
Organization~/.cache/cfactory/org/<orgId>/hooks/hooks.json (pulled via org sync)org hooks cache
Project.cfactory/hooks.jsonproject root
Global~/.config/cfactory/hooks.json~/.config/cfactory/
Cursor compat.cursor/hooks.jsonproject root

Precedence (highest wins on updated_input / messages): managed > org > project > cursor > global. permission: deny always wins. When cfactory.org.hooks.enforce (or hooks.enforce from /api/config) is true, an org/managed deny cannot be relaxed by lower-priority sources.

Org hooks are never copied into the git worktree — only into the org cache, same as skills/commands.

Example

{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      {
        "command": ".cfactory/hooks/block-network.sh",
        "matcher": "curl|wget|nc ",
        "failClosed": true
      }
    ],
    "afterFileEdit": [
      {
        "command": ".cfactory/hooks/format.sh"
      }
    ]
  }
}

Command hooks receive JSON on stdin and may print JSON on stdout.

  • Exit 0 — use JSON output
  • Exit 2 — deny (same as permission: "deny")
  • Other non-zero — fail-open unless failClosed: true

Environment variables: CFACTORY_PROJECT_DIR, CFACTORY_VERSION, and CLAUDE_PROJECT_DIR (alias).

Agent events

EventControl
sessionStart / sessionEndInject env / additional_context; audit end
preToolUse / postToolUse / postToolUseFailureDeny/rewrite input; inject additional_context
subagentStart / subagentStopAllow/deny Task; followup_message
beforeShellExecution / afterShellExecutionAllow/deny/ask shell; audit output
beforeMCPExecution / afterMCPExecutionAllow/deny/ask MCP
beforeReadFile / afterFileEditDeny reads; post-process edits
beforeSubmitPromptBlock submit (continue: false)
preCompactObserve compaction
stopfollowup_message with loop_limit (default 5)
afterAgentResponse / afterAgentThoughtObserve assistant text / reasoning

Deferred (not in v1)

  • Tab hooks (beforeTabFileRead, afterTabFileEdit)
  • workspaceOpen
  • Enterprise MDM / cloud team sync

Plugin mapping

hooks.json / CursorPlugin hook
preToolUsepreToolUse (+ legacy tool.execute.before)
postToolUsepostToolUse (+ legacy tool.execute.after)
beforeSubmitPromptbeforeSubmitPrompt (+ chat.message)
preCompactpreCompact (+ experimental.session.compacting for mutation)
afterAgentResponseafterAgentResponse (+ experimental.text.complete)
shell.env, chat.params, auth, provider, config, event (plugin-only)

See Plugins for the TypeScript API.

Debugging

Hook runs are logged by the CLI and published on the event bus as hooks.run (start / success / error / denied / skipped). In the IDE, open Settings → Agent Behaviour → Hooks.