Skip to content

[pull] main from gi-dellav:main#96

Merged
pull[bot] merged 11 commits into
lenylvt:mainfrom
gi-dellav:main
Jul 10, 2026
Merged

[pull] main from gi-dellav:main#96
pull[bot] merged 11 commits into
lenylvt:mainfrom
gi-dellav:main

Conversation

@pull

@pull pull Bot commented Jul 10, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

Note

Add a hooks system that gates and rewrites tool calls, prompts, and session lifecycle events

  • Introduces a hooks Cargo feature (off by default) that wraps every registered tool with HookedTool, dispatching PreToolUse/PostToolUse/PostToolUseFailure shell hooks that can deny, rewrite, or allow calls via exit codes and JSON on stdout/stderr.
  • Adds a HookDispatcher loaded from global, project, and managed settings.json files; project-level hooks require interactive trust confirmation before activation, stored in trusted-hooks.json.
  • Hooks are invoked as subprocesses in their own process groups with configurable timeouts; a LoopInfo context allows Stop hooks to force agent continuation up to 8 consecutive times with a hook-provided instruction.
  • Adds --no-hooks, --hooks-test <tool>, and --hooks-test-input CLI flags; a /hooks slash command reports configured event/handler counts; session start/end and subagent lifecycle events are dispatched around key state transitions.
  • Extends PermissionChecker with force_ask_once and allow_once one-shot methods consumed on the next permission check, feeding hook-denied calls into doom-loop detection via record_blocked.
  • Risk: AnyAgent::spawn_runner is now async and run_print drops its max_turns parameter, which are breaking API changes for any callers outside this PR.

Macroscope summarized bf6bcb6.

xavierforge and others added 11 commits July 9, 2026 21:55
Introduce a default-off `hooks` feature under src/extras/hooks/: a rig-free
HookDispatcher (config schema, matcher index, stdin envelope, exit-code and
stdout-JSON channels, deterministic merge/dedup, subprocess execution with
timeout and process-group cleanup), the single rig-typed adapter (HookedTool
plus wrap_all), and the per-hash trust model.

Add the permission-seam methods the adapter needs (force_ask_once, allow_once,
record_blocked reusing track_doom_loop), gated behind the feature so the
default build is unchanged.
Cover the dispatcher engine, the rig adapter, the trust model, lifecycle
dispatch, and the permission seam, plus the data-driven contract corpus. All
hook tests live under src/tests/hooks/ to keep the flat test directory readable.
Weave the tool-event decorator into the agent and subagent builders through a
single wrap_from_global point. Dispatch the lifecycle events from zerostack's
own runner: UserPromptSubmit, Stop (including the headless -p/--loop path),
SessionStart/SessionEnd, and SubagentStart/SubagentStop. Add the --no-hooks
kill switch and the hooks-test dry-run entry point.
Add a Windows managed-settings path and a powershell -Command fallback
for build_shell_invocation, following the sh/powershell convention
already used in main.rs and event_handler.rs. Document the Windows
settings paths (experimental) and platform-aware command wording in
CONFIG.md, and make the subprocess tests cfg-aware so they stay
meaningful on the Unix CI runner.
hash_hook_binding omitted the project path, so trusting a binding like
{"command": "./guard.sh"} in one project silently trusted the
identical binding in any other project, executing that project's file
since hook subprocesses inherit zerostack's CWD. Thread project_root
through hash_hook_binding, filter_trusted_project_hooks, and
build_dispatcher_from_paths as an explicit parameter (keeping the
dispatcher builder fully unit-testable without touching the CWD), and
have load_dispatcher pass the canonicalized current directory, falling
back gracefully and never panicking if canonicalization fails.

Add a test proving two different project roots produce different
hashes for the same event/matcher/handler, and a test proving trust
does not carry over across build_dispatcher_from_paths calls with
different roots.
runner::run_print took _max_turns but never used it: rig 0.39 seeds
each request's turn bound from agent.default_max_turns, set once at
construction (agent::builder::build_agent_inner) from the same
cli.resolve_max_agent_turns(cfg) value, so behavior is unchanged
without the parameter. Remove it from run_print, its AnyAgent wrapper,
and both call sites; drop the now-unnecessary
#[allow(clippy::too_many_arguments)].

Also replace the Option<(u32, bool)> loop_info tuple with a documented
LoopInfo { iteration, active } struct across spawn_agent, run_print,
and their AnyAgent wrappers, so the Stop hook envelope fields it feeds
are self-explanatory at the call site instead of positional.
…ny rule

Add a test tool that mirrors bash.rs's real check_perm flow (parses
args as {"command": "..."} and checks the parsed command, not the raw
JSON), then configure a PreToolUse hook that rewrites an innocuous
command into one matched by a permission deny rule. Asserts the call
still errors with the permission denial, proving decorator.rs applies
updated_input before the inner call and the inner tool's own
permission check sees the rewritten command rather than the original.
…ments

CONFIG.md claimed copying a CC hooks setup works "with no changes to
the hook scripts themselves". That's true of the settings.json schema,
but not of scripts that read $CLAUDE_PROJECT_DIR: zerostack sets
$ZEROSTACK_PROJECT_DIR instead (a deliberate product-decoupling
decision, not a gap to fix), so such scripts do need a one-line change.
Soften both places that made the stronger claim.

Also add a short comment on decorator.rs's WasmBoxedFuture explaining
it's rig's ToolDyn return type (Send-bound only relaxed on wasm32),
and a doc comment on AnyAgent::spawn_runner explaining why it's async:
the UserPromptSubmit gate must resolve before spawning.
feat(hooks): external-command hooks at agent lifecycle points
@pull pull Bot locked and limited conversation to collaborators Jul 10, 2026
@pull pull Bot added the ⤵️ pull label Jul 10, 2026
@pull pull Bot merged commit bf6bcb6 into lenylvt:main Jul 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants