feat(hooks): add Codex-compatible agent lifecycle hooks - #1785
Merged
Conversation
BitFun could only inspect the hooks other AI applications had configured; it had no way to run a user's own command at an agent lifecycle point. Add native hooks that execute user-configured commands at eleven lifecycle events. The configuration document, event names, stdin payload fields, exit-code semantics, and stdout decision schema match the Codex hook contract, so an existing Codex hook script runs unchanged in either tool. - `bitfun-agent-runtime::native_hooks` owns the portable engine: settings parsing, matcher evaluation, payload construction, process execution with timeouts, and decision merging. - `bitfun-core::native_hooks` owns config discovery, gating, and one typed dispatch helper per event; the coordinator, execution engine, and tool pipeline call those helpers. - Hooks are read from `<user config>/config/hooks.json` and, when explicitly enabled, `<workspace>/.bitfun/config/hooks.json`. Project hooks stay off by default because they execute commands from the checked-out repository. - A PreToolUse `allow` waives the interactive permission prompt only; a permission-policy deny still rejects the call, so hooks can narrow the policy but never widen it. - Hooks are skipped for remote workspaces, where the hook process and the workspace path would not describe the same filesystem. Documented in docs/features/agent-hooks.md (English) and docs/features/agent-hooks.zh-CN.md (Chinese), including the deviations from Codex and the payload fields that are not populated yet. Covered by 56 tests across settings parsing, payload construction, the process interface (real subprocesses for exit codes, timeouts, and stdout decisions), and product wiring.
A PreToolUse hook approval waives the interactive permission prompt only. Cover both halves of that invariant: a policy Deny still rejects the call and the tool never runs, while an Ask-only policy proceeds without raising a permission request.
A handler that reads stdin and echoes it back blocks on its own stdout once the payload exceeds the pipe buffer in both directions. Writing the payload before waiting for the child would deadlock there; the engine drives the write and the wait concurrently, and this pins that.
The hooks capability had no UI: the enablement gates could only be reached by hand-editing app.json. Add a settings page under Smart Capabilities that exposes both gates, explains where hooks.json lives, and links out to the Codex hook reference. Also rewrite the feature docs around the fact that BitFun implements the Codex hook contract rather than a dialect of it. The docs now lead with that compatibility, send readers to https://learn.chatgpt.com/docs/hooks for the event, payload, and decision reference, and keep only what is BitFun-specific: file locations, the app.hooks gates, and an explicit deviations table. That halves their length and removes a second copy of a reference that would otherwise drift out of sync with upstream.
SETTINGS_TAB_SEARCH_CONTENT is a Record<ConfigTab, ...>, so adding a tab without a matching entry fails the type-check. Give the Hooks tab its section titles and descriptions so settings search matches its content.
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds native agent lifecycle hooks: user-configured commands that run at eleven points in the BitFun Agent's lifecycle (tool calls, permission prompts, prompt submission, context compaction, subagents, session/turn boundaries).
BitFun could previously only inspect hooks configured for other AI applications (the read-only
/hookscatalog). There was no way to run your own command when the Agent does something. This adds that.The configuration document, event names, stdin payload fields, exit-code semantics, and stdout decision schema match the Codex hook contract, so an existing Codex hook script runs unchanged in BitFun and vice versa.
Type and Areas
Type: Feature
Areas: Rust core (
assembly/core,execution/agent-runtime), web UI (settings), docs, localesMotivation / Impact
Hooks are the standard way users automate policy around a coding agent: block edits to generated files, run a formatter after every write, require a clean test run before the Agent finishes, inject project context at session start, or auto-approve a tool that is always safe. Codex, Claude Code, and OpenCode all ship this; BitFun could only look at their configuration files.
Matching the Codex contract exactly (rather than inventing a BitFun-specific one) means users bring their existing scripts with them, and the repo's existing
codex-adapterunderstanding of that format stays the single source of truth.Impact when no hooks are configured: none — dispatch early-outs before any process work.
Settings UI
Settings → Smart Capabilities → Agent Hooks exposes both gates, states where
hooks.jsonlives on each platform, and links out to the Codex reference. Without it the gates could only be reached by hand-editingapp.json. Newsettings/hooksi18n namespace in en-US / zh-CN / zh-TW.Docs approach
BitFun implements the Codex hook contract rather than a dialect of it, so the docs lead with that and send readers to https://learn.chatgpt.com/docs/hooks for the event, payload, and decision reference. What stays is only BitFun-specific: file locations, the
app.hooksgates, and an explicit deviations table ("not supported" / "fields not populated yet" / "behavior worth knowing"). This keeps the pages roughly half their original length and avoids maintaining a second copy of a reference that would drift out of sync with upstream.Architecture
Follows the layering in
src/crates/*/AGENTS.md:bitfun-agent-runtime::native_hooks— portable engine: settings parsing, matcher evaluation, payload construction, process execution with timeouts, decision merging. No dependency on assembly/adapters/apps.bitfun-core::native_hooks— product wiring: config discovery, gating, one typed dispatch helper per event. The coordinator, execution engine, and tool pipeline call those helpers instead of executing hooks inline.Three separate things now share the word "hook"; the AGENTS.md update spells out the split: these native user hooks, the internal compiled-in
post_call_hooks, and the read-only external hook catalog (external_hooks).Security posture
app.hooks.project_hooks_enabled). A project hook file executes commands from the checked-out repository, so anyone who can land a commit could otherwise run code on a reviewer's machine.PreToolUseallowwaives the interactive prompt only; a tool call denied by a permission rule stays denied.Verification
Fully tested. 59 tests added, all passing:
The two
hook_approvaltests pin the security invariant in the tool pipeline:a hook approval waives the interactive prompt, but a policy
Denystill rejectsthe call and the tool never runs.
The execution tests spawn real subprocesses to pin the process contract: payload delivery on stdin, exit code 0 with JSON decisions, exit code 2 blocking with stderr as the reason, other exit codes warning without blocking, timeouts killing the handler, and matcher selection.
Also run:
Not run locally: the frontend checks (
type-check:web,i18n:audit, web-ui tests) — this machine has no Node toolchain installed, so the settings page and locale files are verified by CI's Frontend Build. Key parity across the three locales and a one-to-one match between the panel's 23t()calls and its defined keys were checked by script.Also not run locally:
cargo check -p bitfun-desktop—leptonica-sys(OCR native library) fails to build in this environment, which is pre-existing and unrelated to this change. No desktop code is touched and no Tauri command is added, soremote_workspace_policy.rsneeds no new entry. Leaving that to CI.Reviewer Notes
This work is AI-assisted. It was reviewed by a multi-agent adversarial pass (correctness / security / Codex-fidelity / integration reviewers, each finding independently verified by skeptics). Defects that pass found and that are fixed in this PR:
app.hooks.enabled = falsewas silently ignored. The test that was supposed to cover it was vacuous; it now pins real path resolution.PreToolUseallowbypassed the whole permission evaluation, including hardDenyrules. It now feeds into evaluation instead of replacing it, with two tests pinning the invariant.Stopfired for subagent turns, whichSubagentStopalready covers.Deliberately left for follow-ups (all low impact; fixing them here would cost more risk than it buys):
enabled: falseis not honored in hosts that never initialize the service.updatedInputrewrite lands after the concurrency-safety batching decision, so a call rewritten from a read into a write could still run inside a parallel batch. Reorderingexecute_toolsfor this is riskier than the narrow scenario warrants.Known gaps are documented rather than implied — see the "Current gaps" section of the docs.
transcript_pathis always null,SessionStart.sourceis alwaysstartup,permission_modeonly takes two of the five Codex values, andSubagentStopdoes not fire on subagent failure. These are honest deviations, not silent ones; they are the natural follow-ups.Docs:
docs/features/agent-hooks.mdanddocs/features/agent-hooks.zh-CN.md— full event reference, the process interface, worked examples, a security section, and a troubleshooting table. Linked from README (both languages) and AGENTS.md/AGENTS-CN.md.Checklist