Skip to content

feat(kanban): fire kanban_worktree_created hook after git worktree add - #104226

Open
rodrigogs wants to merge 1 commit into
NousResearch:mainfrom
rodrigogs:feat/kanban-worktree-created-hook-upstream
Open

rodrigogs wants to merge 1 commit into
NousResearch:mainfrom
rodrigogs:feat/kanban-worktree-created-hook-upstream

Conversation

@rodrigogs

@rodrigogs rodrigogs commented Sep 6, 2026 •

Copy link
Copy Markdown

What does this PR do?

A card created with --workspace worktree gets a linked git worktree, and a linked worktree is a
fresh checkout: it holds tracked files and nothing else. Everything git ignores is absent by
definition — and for most real projects that includes the one file the project cannot start without.
Measured on the install this comes from: one repo keeps 780 lines across gitignored .env and
.env.dev, 33 credential-shaped keys each. The worker lands in a repository that looks complete and
fails at the first command that reads configuration, with an error about the application rather
than about the checkout. It isn't even reportable as a kanban bug — that checkout is exactly what
git worktree add promises.

Which secrets a project needs, and where they are kept, is deployment knowledge that no code here can
hold. So this adds a seam, and it adds it through the hook system Hermes already has rather than a
new one: right after git worktree add succeeds — and before any worker is spawned —
_ensure_git_worktree fires a new kanban_worktree_created plugin hook with task_id, board,
profile_name, worktree_path, repo_root and branch. A Python plugin subscribes with
ctx.register_hook("kanban_worktree_created", ...); an operator who just wants a script subscribes
through the existing hooks: block in config.yaml:

hooks:
  kanban_worktree_created:
    - command: "~/.hermes/agent-hooks/seed-worktree.sh"
      timeout: 120
      fail_closed: true

The script gets the payload as JSON on stdin (extra.worktree_path, extra.repo_root, ...),
exactly like every other shell hook. With no subscriber nothing changes.

Why this shape (review feedback on the first revision). The first revision ran a bare executable
at $HERMES_HOME/hooks/worktree-created. That put a third, incompatible hook convention inside the
directory the gateway HookRegistry already owns, invisible to hermes hooks list/doctor, and its
stated reason for not reusing the hooks: bridge was wrong on both counts (the bridge does accept
consent without a TTY via --accept-hooks / HERMES_ACCEPT_HOOKS / hooks_auto_accept, and it fires
on every VALID_HOOKS event, not just tool calls). The accurate gap was narrower: no worktree-created
event existed, and non-tool shell-hook events cannot block. This revision closes exactly that gap:

  • kanban_worktree_created joins VALID_HOOKS (hermes_cli/plugins.py) as the one kanban gate
    rather than observer. Like the other kanban_* hooks it runs synchronously on the caller's thread
    and is not timeout-bounded (_HOOK_TIMEOUT_BOUNDED_HOOKS is unchanged), so a hook that runs
    npm ci is not abandoned at plugins.hook_callback_timeout; a shell hook enforces its own
    per-entry timeout (max 300s).
  • In agent/shell_hooks.py the event joins _BLOCKING_EVENTS and gets a block-only stdout parser,
    so exit code 2 (stderr becomes the message) and fail_closed: true (timeout, unstartable
    command) now produce the same {"action": "block", "message"} a pre_tool_call hook would.
    Everything else — allowlist consent, split_command_line without a shell, own process group plus
    kill_process_tree on timeout, hermes hooks list/doctor/test — is reused, not re-implemented.
    hermes hooks test kanban_worktree_created gains a synthetic payload with the runtime shape.

Failure is atomic, as before. A block result makes the caller remove the just-created worktree
and raise with the hook's message. That raise lands in dispatch_once's workspace guard
(hermes_cli/kanban_db_dispatch.py, the except around _resolve_worktree_workspace), which records
a spawn_failed and releases the claim, so the card returns to ready and is retried up to
failure_limit. Removing the tree first is what keeps that retry honest: a worktree left behind
would take _ensure_git_worktree's idempotent early return, skip both git worktree add and the
hook, and spawn the worker into the unseeded checkout — the exact outcome the block exists to
prevent, one tick later and with no error attached. git worktree remove --force is required (a hook
that failed halfway leaves untracked files, and git refuses to remove a dirty worktree); the
branch is kept (it may have existed before the call, and the retry's worktree add <target> <branch> works on it once the tree is gone).

One residual, stated rather than glossed: if the removal itself fails, the tree stays and the next
retry does spawn into it. Nothing here can prevent that, so the reason says so and names the path to
remove by hand. Both branches put the retry-safety sentence before the hook's text and before
git's error, because the reason reaches the card through _record_task_failure, which keeps
error[:500]; the two caps (300 chars of hook text, 120 of git's refusal) only bound the tail. Two
tests pin that the verdict survives the 500-char cut with a 5000-char hook message in both branches.

Where the hooks: block gets wired (review feedback on the second revision). Shell hooks reach
the plugin manager only through agent.shell_hooks.register_from_config, which the CLI calls from
_prepare_agent_startup for agent commands (hermes, chat, acp, rl, cron run|tick,
gateway run, mcp serve) and the gateway calls at startup — so the gateway-hosted dispatcher and
interactive hermes had the seed script, but hermes kanban claim, a one-shot
hermes kanban dispatch and the standalone hermes kanban daemon --force did not: kanban is
not an agent command, the hook fired to an empty subscriber list, the worktree was created unseeded
with no warning, and every later tick reused it through the idempotent early return. Python plugin
callbacks were unaffected (invoke_hook discovers plugins lazily); the gap was specific to the
hooks: path the docs call the shortest. This revision adds _register_config_hooks(args) to
hermes_cli/kanban_ops.py — the same three lines as GatewayStartupMixin._register_config_hooks:
register_from_config(load_config(), accept_hooks=args.accept_hooks), never raising — and calls it
in _cmd_dispatch (skipped on --dry-run, which creates no workspace), in _cmd_daemon after
init_db(), and in _cmd_claim right before resolve_workspace. Consent is unchanged: allowlist,
--accept-hooks (an inherited global flag, so hermes kanban claim --accept-hooks works),
HERMES_ACCEPT_HOOKS, hooks_auto_accept, or the TTY prompt. Adding kanban to
_AGENT_SUBCOMMANDS in hermes_cli/main.py was the alternative; it was rejected because that
mapping also drives inline MCP server discovery and plugin pre-warming, which a dispatcher process
does not need. Three tests, one per path, run the real command bodies against a real kanban DB and a
hooks: block in a real config.yaml and fail without the change.

A subtlety worth knowing as a hook author, documented in three places: only a block fails the
card.
invoke_hook logs and skips a Python callback that raises, and the bridge treats any
non-zero exit other than 2 as a warning (its normal fail-open rule). So return the directive / exit 2
on the failing path, and set fail_closed: true so a hang or a missing script cannot pass an
unseeded worktree either.

Deliberately not changed: _cleanup_worktree_workspace (the completion-time pruner preserves
anything dirty or unpushed, which is precisely what a half-seeded tree looks like);
_resolve_worktree_workspace's fallback paths, worktree teardown and scratch/dir: workspaces
(they only gain the task_id/board kwargs threaded to the fire site); hermes kanban claim's
claim handling — it now wires the hooks: block and fires the same hook, but still has no dispatcher
to release the claim, so a block there leaves the card claimed with no workspace until the claim TTL
expires. That pre-existing gap applies to every workspace failure in _cmd_claim and is documented
in kanban.md rather than patched here. Outbound webhooks (outbound_webhooks:) are likewise still
registered only by the agent commands and the gateway; the new helper wires shell hooks only.

Based on 693641aa8b.

Related Issue

No issue — the mechanism has been carried as a local patch against a vendored checkout and is being
upstreamed. Nothing open covers it (see the duplicate-check note below).

Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • hermes_cli/kanban_db_workspace.py — _ensure_git_worktree takes keyword-only task_id / board
    (threaded from _resolve_worktree_workspace and _anchored_worktree) and calls the new
    _fire_worktree_created_hook right after git worktree add succeeds. That helper calls
    hermes_cli.lifecycle.invoke_hook("kanban_worktree_created", ...); the first
    {"action": "block"} result (or an exception from invoke_hook itself) runs
    _discard_unseeded_worktree (git worktree remove --force via the module's _git helper) and
    raises RuntimeError naming the worktree, the removal outcome, then the hook's message.
    _HOOK_MESSAGE_LIMIT = 300, _REMOVAL_DETAIL_LIMIT = 120.
  • hermes_cli/plugins.py — "kanban_worktree_created" added to VALID_HOOKS with its contract in
    the adjacent comment.
  • agent/shell_hooks.py — _BLOCKING_EVENTS now {"pre_tool_call", "kanban_worktree_created"};
    _parse_block_directive (the block half of _parse_pre_tool_call, which now calls it) registered
    as the event's stdout parser.
  • hermes_cli/kanban_ops.py — new _register_config_hooks(args) (shell-hook registration from
    config.yaml, mirroring GatewayStartupMixin._register_config_hooks; never raises), called from
    _cmd_dispatch (not on --dry-run) and _cmd_daemon (after init_db()).
  • hermes_cli/kanban.py — _cmd_claim calls _register_config_hooks(args) before
    resolve_workspace.
  • hermes_cli/hooks.py — _DEFAULT_PAYLOADS["kanban_worktree_created"] so hermes hooks test /
    doctor send the runtime payload shape; doctor now reports a block verdict on the synthetic
    payload as ✓ returned a block on the synthetic payload (exit=2, …): <message> instead of the
    observer-only line it printed for any empty stdout regardless of exit code (the documented seed
    script exits 2 against doctor's /tmp/repo, so this is the line operators will see).
  • agent/shell_hooks.py (module docstring) — exit-2 sentence names both blocking-capable events.
  • tests/hermes_cli/test_kanban_worktree_created_hook.py — new, 13 cases.
  • tests/hermes_cli/test_hooks_cli.py — one new doctor case: an allowlisted exit-2 gate on the
    synthetic payload is reported as a block, not as observer-only (fails on the previous revision).
  • website/docs/user-guide/features/hooks.md — catalog row, a kanban_worktree_created section
    under the kanban hooks (heading renamed from "Kanban lifecycle observers" to "Kanban lifecycle
    hooks" and the "all three are observer-only" summary now says the fourth is a gate), the exit-2 /
    fail_closed prose names both blocking-capable events, and the "Shell hooks are registered by…"
    sentence lists the standalone kanban entry points.
  • website/docs/user-guide/features/kanban.md — the worktree bullet now says the dispatcher
    creates the worktree (it does; the old "worker-side" wording was stale) and points at a rewritten
    "Seeding a worktree workspace" subsection with the config.yaml entry, a worked script, the
    atomic-failure semantics, the "only a block fails the card" rule, which entry points wire the
    hooks: block, what hermes hooks doctor prints for the seed script, and the
    hermes kanban claim caveat.
  • website/docs/user-guide/features/plugins.md, website/docs/developer-guide/plugins/index.md —
    hook tables gain the event; plugins.md drops the stale "26 lifecycle events" count (the set is 38
    after this change; the sentence already defers to the canonical catalog).

No config keys, no schema changes, no new dependencies, no new files under $HERMES_HOME.

How to Test

  1. scripts/run_tests.sh tests/hermes_cli/test_kanban_worktree_created_hook.py -q → 13 passed.
    Plugin-side cases register a callback on the real plugin manager; shell-side cases go through
    agent.shell_hooks.register_from_config with a real Python script and a real git worktree add;
    CLI cases run the hermes kanban claim / dispatch / daemon --force command bodies against a
    real kanban DB and a hooks: block written to a real config.yaml (hooks_auto_accept: true).
    No subprocess or invoke_hook mocks; the two stand-ins are the removal-failure case, which
    stubs the module's _git wrapper for worktree remove only, and the dispatch/daemon cases,
    which stub _default_spawn (the hermes chat worker) and, for the daemon, run one iteration of
    the loop body instead of run_daemon's wait-for-signal loop. Cases: payload fields and
    after-checkout timing, once-per-creation, no-subscriber no-op, block → discard → raise (with the
    retry verdict ahead of the hook text), retry re-creates and re-fires, verdict inside the 500-char
    card budget when the tree was removed and when git worktree remove fails, a hooks: script
    seeding the worktree from the stdin payload, exit 2 failing the card with its stderr, a
    fail_closed timeout (3 s, so two interpreter start-ups fit on a loaded runner) that fails the
    card and kills the hook's grandchild (the npm ci stand-in), and the three CLI paths each
    handing over a worktree the configured script has seeded — all three fail on the previous
    revision.

  2. Regression, same runner, on the suites this touches — shell-hook bridge, hermes hooks CLI,
    plugin manager, kanban lifecycle/worker hooks, and the eight worktree-touching kanban files
    (test_kanban_board_project, test_kanban_boards, test_kanban_db,
    test_kanban_gateway_restart_handoff, test_kanban_project_link, test_kanban_transfer,
    test_kanban_worktree_isolation, test_kanban_worktree_teardown) plus test_shell_hooks,
    test_shell_hooks_consent, test_shell_hooks_tree_kill, test_hooks_cli, test_plugins,
    test_kanban_lifecycle_hooks, test_kanban_worker_lifecycle_hooks, plus
    test_kanban_cli_dispatch_passthrough (drives _cmd_dispatch) and this PR's file: 17 files, 259 passed, 5 skipped, 0 failed.

  3. Manually, on a repo with a gitignored .env, in the dispatcher's profile:

    # ~/.hermes/config.yaml
    hooks:
      kanban_worktree_created:
        - command: "~/.hermes/agent-hooks/seed-worktree.sh"
          timeout: 120
          fail_closed: true
    #!/usr/bin/env bash
    payload="$(cat -)"
    worktree=$(jq -r '.extra.worktree_path' <<<"$payload")
    repo=$(jq -r '.extra.repo_root' <<<"$payload")
    ln -s "$repo/.env" "$worktree/.env" || { echo "no .env in $repo" >&2; exit 2; }

    Approve once (hermes at a TTY, or hooks_auto_accept: true), check hermes hooks doctor
    (against its synthetic /tmp/repo payload this script exits 2, and doctor now says
    ✓ returned a block on the synthetic payload (exit=2, …): no .env in /tmp/repo), then
    hermes kanban create "touch .env" --assignee <profile> --workspace worktree.
    <repo>/.worktrees/<id>/.env exists — whether the card was picked up by the gateway dispatcher,
    by hermes kanban dispatch, or claimed by hand with hermes kanban claim <id>. Remove the repo's
    .env → the card fails with no .env in … in the reason, git worktree list shows no leftover
    tree, and the next dispatch tick re-creates it and fires the hook again.

Checklist

Code

Not ticking the full-suite box: what I ran is the 17-file set above through scripts/run_tests.sh
(CI parity: clean env, per-file subprocess isolation), all green.

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A

  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A (N/A: no new
    config key; the existing hooks: block gains an event name, documented in hooks.md and kanban.md)

  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A (N/A)

  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A

    There is no executable-bit gate anymore (the first revision's os.access(X_OK) degraded to an
    existence check on Windows). The command runs through the bridge's split_command_line with
    shell=False, so a Windows entry is command: "python C:\\...\\seed.py" like any other shell
    hook; timeout cleanup is the bridge's kill_process_tree (os.killpg on POSIX, taskkill /T on
    Windows). The removal path goes through the module's _git helper. Tests drive Python scripts via
    sys.executable rather than /bin/sh; scripts/check-windows-footguns.py is clean on the changed
    files.

  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A (N/A: no tool schema)

Screenshots / Logs

Blocked hook, as the card's failure reason:

workspace: kanban_worktree_created hook blocked the worktree /…/repo/.worktrees/t_fail; the unseeded
worktree was removed, so a retry re-creates and re-seeds it; hook: no .env for this project

Shell hook that hung under fail_closed: true (bridge warning, then the card reason):

shell hook timed out after 3.03s (event=kanban_worktree_created command=…/seed-worktree.sh)
workspace: kanban_worktree_created hook blocked the worktree /…/repo/.worktrees/t_hang; the unseeded
worktree was removed, so a retry re-creates and re-seeds it; hook: hook …/seed-worktree.sh failed
closed: timed out after 3s

@alt-glitch alt-glitch added type/feature New feature or request comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have labels Sep 6, 2026
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

PR #104226 — feat(kanban): run worktree-created hook after git worktree add

Well-reasoned operator seam: linked worktrees lack gitignored secrets by definition, and the fix (a $HERMES_HOME/hooks/worktree-created hook with documented argv/cwd/env) puts deployment knowledge where it belongs. The failure-atomicity analysis in _discard_unseeded_worktree is the best part — it correctly identifies the early-return retry hole and closes it.

What looks right

  • hermes_cli/kanban_db_workspace.py (_run_worktree_created_hook): absent hook = no behavior change; present-but-not-executable = skipped with a debug line (draft-friendly); HERMES_HOME pinned in the child env so hooks can't land in another profile.
  • subprocess.run uses argv-list form, no shell=True; stdout capped at 2000 chars in logs, stderr at 300 in the error (ordered before the retry-safety sentence because _record_task_failure truncates at 500 — thoughtful).
  • Failed hook discards the tree with worktree remove --force (required: hooks leave untracked files behind) and deliberately leaves the branch (it may predate the call).
  • Tests drive real executable hooks against real git worktree add, no subprocess mocks.

Non-blocking points

  • The hook inherits the full parent environment ({**os.environ, ...}), so process secrets reach it. It's operator-owned code, so this is acceptable — just noting the trust boundary is "your own hook", not a sandbox.
  • 120s hook budget runs on the dispatcher's thread and stalls every card on the tick. Documented as a deliberate tradeoff; if hooks get slow in practice, consider off-thread execution later.
  • Residual risk (removal itself failing → retry spawns into unseeded tree) is honestly documented with a manual-removal instruction. Nothing more to do in code.

@rodrigogs
rodrigogs force-pushed the feat/kanban-worktree-created-hook-upstream branch from ca0bac3 to a310e56 Compare September 7, 2026 02:18
@rodrigogs rodrigogs changed the title feat(kanban): run worktree-created hook after git worktree add feat(kanban): fire kanban_worktree_created hook after git worktree add Sep 7, 2026
@rodrigogs
rodrigogs force-pushed the feat/kanban-worktree-created-hook-upstream branch 2 times, most recently from 9bada68 to fa78dc3 Compare September 13, 2026 17:31
@rodrigogs

Copy link
Copy Markdown
Author

Rebased onto upstream/main (5dea46d13d); GitHub reported this PR CONFLICTING before the push (the daily fork keeper also stopped on it at 09:01 today).

What the force-push overwrites: the previous remote head 9bada6800 is replaced by fa78dc3e5. Same single commit, same 12 files (+759/−26).

One conflict, in website/docs/user-guide/features/plugins.md under Available hooks — an anchor clash, not a disagreement: upstream now writes the count literally (Plugins can register the 27 lifecycle events currently accepted by hermes_cli.plugins.VALID_HOOKS), while this PR's hunk deliberately drops the count so the sentence does not need re-bumping every time an event is added. The resolution keeps the count-free sentence — this PR's side — and leaves the kanban_worktree_created row in the directive/control table untouched.

Nothing else moved: git range-diff reports the commit changed only in that conflicted hunk, and the auto-merged files carried through as-is — hermes_cli/kanban_db_workspace.py (_run_worktree_created_hook), hermes_cli/hooks.py, hermes_cli/kanban.py, hermes_cli/kanban_ops.py (_register_config_hooks), hermes_cli/plugins.py, agent/shell_hooks.py (_BLOCKING_EVENTS).

Verified on the rebased head under the repo's hermetic runner (scripts/run_tests.sh): tests/hermes_cli/test_kanban_worktree_created_hook.py → 13 passed; tests/hermes_cli/test_hooks_cli.py + tests/agent/test_shell_hooks.py → 56 passed. 69 total, 0 failed.

GitHub now reports this PR mergeable: true. No new ask.

A card created with `--workspace worktree` gets a linked git worktree, and a
linked worktree is a fresh checkout: tracked files and nothing else. Everything
git ignores is absent by definition, and for most real projects that includes
the `.env` the application cannot start without. The worker lands in a
repository that looks complete and fails at the first command that reads
configuration, with an error about the application rather than the checkout.

Which secrets a project needs, and where they live, is deployment knowledge no
code here can hold, so this adds a seam through the hook system that already
exists rather than a new one. `_ensure_git_worktree` fires the new
`kanban_worktree_created` plugin hook (hermes_cli/kanban_db_workspace.py)
right after `git worktree add` succeeds and before any worker spawns, with
task_id, board, profile_name, worktree_path, repo_root and branch. A plugin
subscribes with ctx.register_hook; a shell script subscribes through the
`hooks:` block in config.yaml like every other event, so `hermes hooks
list/doctor/test` already know it and consent is the bridge's existing
allowlist (TTY prompt, hooks_auto_accept, HERMES_ACCEPT_HOOKS). With no
subscriber nothing changes.

Unlike the other kanban_* hooks this one is a gate: a `{"action": "block",
"message"}` result (shell bridge: exit 2, or fail_closed on a timeout or
spawn failure) makes the caller remove the just-created worktree and raise
with the message. That raise lands in dispatch_once's workspace guard, which
records a spawn_failed and releases the claim so the card is retried; removing
the tree first is what keeps that retry honest, because a worktree left behind
would take `_ensure_git_worktree`'s idempotent early return, skip both the
`git worktree add` and the hook, and spawn the worker into the unseeded
checkout with no error attached. `git worktree remove --force` is required
(a hook that failed halfway leaves untracked files); the branch is kept. If
the removal itself fails the reason says so and names the path to remove by
hand. The retry-safety sentence is placed before the hook's text and before
git's error so `_record_task_failure`'s 500-char cut cannot drop it.

Shell hooks only reach the plugin manager through register_from_config, which
the CLI runs for agent commands (_prepare_agent_startup) and the gateway runs
at startup; `hermes kanban` is neither, so `hermes kanban claim`, a one-shot
`hermes kanban dispatch` and `hermes kanban daemon --force` fired the hook to
an empty subscriber list and handed over an unseeded tree with no warning.
kanban_ops.py gains `_register_config_hooks(args)` (the gateway's three
lines: load_config, register_from_config with the inherited --accept-hooks
flag, never raising), called in _cmd_dispatch (not on --dry-run), _cmd_daemon
after init_db and _cmd_claim before resolve_workspace. Python plugin hooks
were never affected (invoke_hook discovers plugins lazily).

Bridge changes are two entries: `kanban_worktree_created` joins
`_BLOCKING_EVENTS` in agent/shell_hooks.py (exit 2 and fail_closed now apply
to it) and gets a block-only stdout parser; `hermes hooks test/doctor` gains a
synthetic payload for the event, and doctor reports a block verdict on that
payload as such instead of the "observer-only" line it printed for any empty
stdout. Process-group kill on timeout, consent and non-TTY acceptance are the
bridge's existing behaviour, reused rather than re-implemented.

Tests: tests/hermes_cli/test_kanban_worktree_created_hook.py, 13 cases
against a real plugin manager and real `hooks:` registration with real
scripts and real `git worktree add` (no subprocess or invoke_hook mocks; the
removal-failure case stubs the module's `_git` wrapper for `worktree remove`,
the CLI cases stub the worker spawn): payload and after-checkout timing,
once-per-creation, no-subscriber no-op, block -> discard -> raise, retry
re-fires, retry verdict inside the 500-char card budget in both removal
branches, shell hook seeding from the stdin payload, exit 2 with stderr as
the reason, fail_closed timeout that also kills the hook's grandchild, and
claim / dispatch / daemon --force each seeding through a `hooks:` block in a
real config.yaml. tests/hermes_cli/test_hooks_cli.py: doctor reports the
block.

Docs: hooks.md (catalog row, kanban_worktree_created section under the
renamed "Kanban lifecycle hooks" heading, blocking-event wording for exit 2 /
fail_closed, which entry points register shell hooks), kanban.md (seeding
subsection with a config.yaml + script example, which paths wire the hooks:
block, what doctor prints for the seed script, `hermes kanban claim` caveat),
plugins.md and developer-guide/plugins/index.md hook tables.
@rodrigogs
rodrigogs force-pushed the feat/kanban-worktree-created-hook-upstream branch from fa78dc3 to f811f62 Compare September 27, 2026 16:53
@rodrigogs

Copy link
Copy Markdown
Author

Rebased onto current main (6f7a7991bb); this PR had gone CONFLICTING — the daily keeper stopped on it, so this was resolved by hand.

What the force-push overwrites: remote head fa78dc3e57 → f811f623ed (single commit, 12 files). Same content plus conflict resolutions, nothing new.

The conflicts, all additive-adjacent:

  • hermes_cli/hooks.py (_DEFAULT_PAYLOADS): upstream added pre_auxiliary_call / post_auxiliary_call payload seeders in the same dict region — both kept, kanban_worktree_created now follows them.
  • hermes_cli/kanban_db_workspace.py: upstream's _path_key(...) comparison hardening landed inside the same hunks that pass task_id=/board= into _ensure_git_worktree and _anchored_worktree — both kept, so the _fire_worktree_created_hook invocation survives with upstream's case-insensitive path keys.
  • tests/hermes_cli/test_hooks_cli.py: upstream added the [Bug]: hermes hooks test hides fail-open/fail-closed decisions on error and timeout #115968 test_print_run_result_shows_decision_for_error_and_timeout + test_hooks_test_distinguishes_fail_closed_from_fail_open at the point where this PR added TestHooksDoctor.test_block_on_synthetic_payload_is_reported_as_a_block — the class method stays inside the class, upstream's two module-level functions follow it.
  • website/docs/.../kanban.md: upstream's dispatcher paragraph now documents worker-reaping/fingerprinting — kept; the worktree bullet keeps this PR's hook link, and keeps "The dispatcher's git worktree add creates it" (verified: _resolve_worktree_workspace is called from kanban_db_dispatch.py, i.e. the dispatcher claim path — upstream's newer "Worker-side" wording contradicts the code).
  • plugins.md: kept upstream's uncounted wording + new ./hooks.md relative link; the Directive/control table row still lists kanban_worktree_created.

Verification: tests/hermes_cli/test_kanban_worktree_created_hook.py + tests/hermes_cli/test_hooks_cli.py: 22 passed; the -k workspace shard: 24 passed, 1 skipped (the two test_plugin_update_transaction.py collection errors also occur on bare main — environment, not this diff).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants