Skip to content

Adapter reference

gitvow hook --agent <claude|codex|gemini|cursor|copilot|factory|opencode|any external name> <Event> reads the agent's payload on stdin, normalises it, runs the corresponding gitvow handler, and answers in the agent's form. --agent claude is the default and changes nothing.

Normalised payload

Every adapter produces the Claude Code shape documented in Hook payloads: session_id, transcript_path, cwd, hook_event_name, tool_name, tool_input. Session ids come from session_id (Codex, Gemini, Factory), conversation_id (Cursor) or sessionId (Copilot, whose payloads are camelCase: toolName, toolArgs).

Event mapping

gitvow handler Codex Gemini Cursor Copilot CLI Factory
SessionStart SessionStart SessionStart sessionStart sessionStart SessionStart
UserPromptSubmit (intent from the first message) not mapped not mapped not mapped not mapped not mapped
PreToolUse PreToolUse BeforeTool preToolUse, beforeShellExecution, beforeMCPExecution preToolUse PreToolUse
PostToolUse PostToolUse AfterTool afterFileEdit, afterShellExecution postToolUse PostToolUse
Stop Stop SessionEnd stop sessionEnd Stop

OpenCode is not in the table because its plugin, written by gitvow install --agent opencode, already speaks gitvow's event names: it calls gitvow hook --agent opencode SessionStart|UserPromptSubmit|PreToolUse|PostToolUse|Stop from OpenCode's session.created, chat.message, tool.execute.before, tool.execute.after and session.idle, with tool_name, tool_input (OpenCode's args), tool_use_id (its callID) and prompt (the text parts of the message).

Responses

  • Codex, Gemini: allow is exit 0 with no stdout; deny and confirm are exit 2 with the message on stderr. Both agents document exit 2 as a block with stderr as the reason.
  • Copilot CLI: stdout JSON {"permissionDecision": "allow" | "deny" | "ask", "permissionDecisionReason": ...}, exit 0. Copilot treats exit 2 as deny too, but JSON is what lets a confirmation become its native ask.
  • Factory: exit 2 with the message on stderr, as documented for PreToolUse.
  • OpenCode: exit 2 with the message on stderr; the plugin throws an error carrying it, which blocks the tool and shows the reason to the agent. Exit 0 is silence. A hook that cannot run at all is a thrown error too: fail closed.
  • Cursor: stdout JSON. Allow is {"permission": "allow"}. Deny is {"permission": "deny", "user_message": ..., "agent_message": ...}. Confirm is {"permission": "ask", ...}, so Cursor prompts the user natively. Exit code 0 in all cases.

Codex patch parsing

apply_patch input is a text in the *** Begin Patch format. gitvow extracts each *** Update File: <path>, *** Add File: <path> and *** Delete File: <path> block; added lines (+) become new_string and removed lines (-) become old_string for that file. Route literals and path rules are evaluated per file; a deny or confirm on any file blocks the whole patch, with the file named in the message.

Install locations

Agent File Notes
Codex ~/.codex/hooks.json hooks must be enabled in ~/.codex/config.toml ([features] hooks = true on older versions)
Gemini ~/.gemini/settings.json under hooks Gemini requires a name per hook entry; gitvow uses gitvow-<event>
Cursor ~/.cursor/hooks.json version: 1; failClosed: true is set so a missing gitvow blocks rather than passes
Copilot CLI ~/.copilot/hooks/gitvow.json version: 1; entries use type: command with bash; Copilot fails closed on non-zero exits for preToolUse
Factory ~/.factory/hooks.json same event schema as Claude Code

Per-repo installs (gitvow install <repo> --agent ...) write the project-level equivalents: .codex/hooks.json, .gemini/settings.json, .cursor/hooks.json, .github/hooks/gitvow.json, .factory/hooks.json.