Kanban boards switch Env Shadow + Delegated-Child Guard
When to use
hermes kanban boards switch Xreports success butboards liststill shows the old board- Three-line probe (
file / env / active) returns inconsistent values delegate_tasksubagent fails any kanban mutation with "delegate_task child contexts cannot mutate Kanban tasks via the CLI"- Dispatcher-spawned worker cannot read or write kanban boards and you need to know which guard tripped
Background
Two related failure modes that make hermes kanban mutations silently fail in Queen-mode sessions. Both surfaced during 2026-08-21 self-audit and shipped with a patch.
Symptom: silent no-op after boards switch
$ hermes kanban boards switch default
Active board is now 'default'. # ← 假装成功
$ cat ~/.hermes/kanban/current
default # ← 文件确实写了
$ hermes kanban boards list | head -2
● proj-c ... # ← 但仍是 proj-c!
boards switch returns success and the file is updated, but the next boards list still shows the old board. All subsequent kanban list/show/create route to the old board.
Root cause (verified 2026-08-21)
hermes_cli/kanban_db.py:get_current_board() (L462-507) resolves in priority order:
_CURRENT_BOARD_OVERRIDEContextVar (per-process; dispatcher uses this for workers)HERMES_KANBAN_BOARDenv var (set by dispatcher when spawning workers; also set by launchers/wrappers/test fixtures)<root>/kanban/currentfile (written byboards switch)- fallback
default
boards switch only writes the file — never touches env. If HERMES_KANBAN_BOARD=proj-c is still in the current shell, env (2) beats file (3) every time. The "switch succeeded" message is a lie; the actual board never changed.
Diagnostic — three-line probe
cat ~/.hermes/kanban/current # file
env | grep HERMES_KANBAN_BOARD # env
hermes kanban boards list | head -2 # effective
If the three don't agree, env wins.
Fix shipped (kanban.py:1312-1348)
_cmd_boards_switch now writes the file, then compares HERMES_KANBAN_BOARD against the new slug. If they differ, os.environ.pop("HERMES_KANBAN_BOARD") and print:
Active board is now 'default' (also cleared HERMES_KANBAN_BOARD env var).
Subparser help text also documents the env-clearing behavior so users know to unset HERMES_KANBAN_BOARD in their parent shell if they want the change to persist across shells (env pop only affects the current process).
Verified after fix:
$ HERMES_KANBAN_BOARD=proj-c bash -c '
hermes kanban boards switch default
hermes kanban boards list | head -2
'
Active board is now 'default' (also cleared HERMES_KANBAN_BOARD env var).
default Default done=19, ready=2
Regression tests added (2026-08-21)
Two CLI tests in tests/hermes_cli/test_kanban_boards.py:
test_switch_clears_stale_env_var— full env-shadow → switch → list round-trip.test_switch_no_env_var_keeps_short_message— guards the original one-line message so the no-env case doesn't get a misleading "also cleared" suffix.
See references/regression-test-recipe.md for the pattern. Critical pitfall:
os.environ.pop only affects the running Python process, so a test that runs
switch and list in two subprocesses with the same env dict won't go red on
the pre-fix code — the env re-inherits every child. The working test strips
HERMES_KANBAN_BOARD from the env dict between the two CLI calls to simulate
the user actually unsetting the variable in their shell (which is what the
new output message instructs them to do).
Symptom: root session can't see active board (env leaked from upstream)
$ env | grep HERMES
HERMES_DELEGATED_CHILD_CONTEXT=1 # ← root session has this
HERMES_KANBAN_BOARD=proj-c
$ hermes kanban list
kanban: could not initialize database: delegate_task child contexts cannot mutate Kanban tasks or boards
$ env -u HERMES_DELEGATED_CHILD_CONTEXT -u HERMES_KANBAN_BOARD hermes kanban list | head
Board: default (6 other boards — `hermes kanban boards list`) # ← real active
The root Queen session gets HERMES_DELEGATED_CHILD_CONTEXT=1 from the upstream process that spawned it (gateway / launch wrapper / session manager). The variable is not in any shell rc file and not in the hermes launcher — it's leaked by whatever spawned the current hermes CLI. Once set, it blocks all kanban mutations and boards list is locked to whatever HERMES_KANBAN_BOARD is set to.
Root cause — verified 2026-08-21 (deeper diagnosis)
scrub_kanban_env in agent/delegation_context.py:138 stamps HERMES_DELEGATED_CHILD_CONTEXT=1 into the cleaned env unconditionally (not gated by the ContextVar):
def scrub_kanban_env(env):
cleaned = dict(env)
for key in KANBAN_ENV_KEYS:
cleaned.pop(key, None)
cleaned[DELEGATED_CHILD_ENV_MARKER] = "1" # ← always writes, even when caller is not delegated
return cleaned
The marker is designed to cross fork boundaries: a real delegate_task child sets _DELEGATED_CHILD_CONTEXT ContextVar to True, calls scrub_kanban_env, and the marker reaches subprocesses so is_delegated_child_process_context() (which checks either the ContextVar or the env var) still flags them as children.
The leak path is: a delegate_task child → terminal subprocess (terminal() tool) → user shell hermes invocation. The terminal subprocess inherits HERMES_DELEGATED_CHILD_CONTEXT=1 from the delegate child's env; the user then runs hermes inside that shell as a root session, but the marker is already in the process env and is_delegated_child_context() (purely ContextVar) is False. is_delegated_child_process_context() still returns True because it falls back to os.environ.get(DELEGATED_CHILD_ENV_MARKER).
Diagnostic to confirm leak vs. genuine child:
# Run inside the suspect hermes session
from agent.delegation_context import _DELEGATED_CHILD_CONTEXT, is_delegated_child_context
import os
print("env marker:", os.environ.get("HERMES_DELEGATED_CHILD_CONTEXT")) # "1" if leaked
print("ContextVar:", _DELEGATED_CHILD_CONTEXT.get()) # False in root session
# If ContextVar is False but env marker is "1" → leaked; safe to scrub
Sanity-check the source files to confirm there is no other writer of the marker:
rg 'os.environ\[.HERMES_DELEGATED_CHILD_CONTEXT.\]\s*=' ~/.hermes/hermes-agent/
rg 'putenv.*HERMES_DELEGATED' ~/.hermes/hermes-agent/
rg 'setenv.*DELEGATED' ~/.hermes/hermes-agent/
All three should return zero hits — only scrub_kanban_env writes it.
Fix — entry-point scrub in hermes_cli/main.py (verified 2026-08-21)
The cleanest fix: when a root hermes CLI process starts, the leaked env marker must NOT be trusted. The CLI is a top-level invocation; its "is this a delegated child?" answer comes from the ContextVar (which only delegate_task sets), not from inherited env. Add a one-line guard at the very top of main() in hermes_cli/main.py, before any other entry-point housekeeping:
def _scrub_leaked_delegated_child_marker() -> None:
"""Drop a leaked HERMES_DELEGATED_CHILD_CONTEXT=1 from a root session.
The marker is set on a delegate_task child via scrub_kanban_env() so
subprocesses spawned from a delegated child inherit the lineage. If the
shell that invokes `hermes` itself inherited the marker (leak from a
previous delegate child's terminal/exec call, or any non-delegated
subprocess), but no _DELEGATED_CHILD_CONTEXT ContextVar is active, the
env var is stale and must NOT block root-session CLI mutations.
Does nothing when the ContextVar is True (genuine delegate child).
"""
try:
from agent.delegation_context import is_delegated_child_context
except Exception:
return
if os.environ.get("HERMES_DELEGATED_CHILD_CONTEXT") == "1" and not is_delegated_child_context():
os.environ.pop("HERMES_DELEGATED_CHILD_CONTEXT", None)
def main():
_scrub_leaked_delegated_child_marker() # ← FIRST line, before _set_process_title, parser, etc.
_set_process_title()
...
Properties:
- One-shot at process entry; cheap (single
os.environ.get+ ContextVar read). - Never raises — agent.delegation_context import is wrapped in
try/except. - No-op when ContextVar is
True(genuine delegate child → marker preserved → guard intact). - Does not relax any trust boundary — the actual mutator guard (
_assert_not_delegated_child_mutationinkanban_db.py:165-181) still fires for real delegated children because it checks both ContextVar AND env. - Place it before
_set_process_title(),_cleanup_quarantined_exes(),_sweep_stale_bytecode_if_checkout_changed(),_recover_from_interrupted_install()— those don't read the marker, but putting it first keeps the scrub visible in the entry-point prologue.
Regression test pattern (pytest, in tests/hermes_cli/test_kanban_boards.py or a new test_cli_entry_scrub.py):
def test_main_scrubs_leaked_delegated_marker(monkeypatch):
monkeypatch.setenv("HERMES_DELEGATED_CHILD_CONTEXT", "1")
monkeypatch.delenv("_DELEGATED_CHILD_CONTEXT", raising=False)
# import main; call _scrub_leaked_delegated_child_marker(); assert env marker gone
from hermes_cli.main import _scrub_leaked_delegated_child_marker
_scrub_leaked_delegated_child_marker()
assert "HERMES_DELEGATED_CHILD_CONTEXT" not in os.environ
def test_main_preserves_marker_when_contextvar_set(monkeypatch):
monkeypatch.setenv("HERMES_DELEGATED_CHILD_CONTEXT", "1")
from agent.delegation_context import _DELEGATED_CHILD_CONTEXT, delegated_child_context
with delegated_child_context():
from hermes_cli.main import _scrub_leaked_delegated_child_marker
_scrub_leaked_delegated_child_marker()
assert os.environ.get("HERMES_DELEGATED_CHILD_CONTEXT") == "1"
Quick escape hatches (still valid)
- Per-command workaround (always works):
env -u HERMES_DELEGATED_CHILD_CONTEXT -u HERMES_KANBAN_BOARD hermes kanban list. - Don't edit
agent/delegation_context.py:scrub_kanban_envto stop writing the marker — real delegated children need it to cross fork boundaries. - Don't
unset HERMES_DELEGATED_CHILD_CONTEXTinside a real delegated child — that defeats the audit trail. - Don't put
HERMES_KANBAN_BOARDin launchd plists / cronEnvironmentVariables— it's a dispatcher-internal signal, not user-facing config.
Symptom: leaf subagent can't mutate kanban
$ # in a delegate_task subagent context
$ hermes kanban boards switch default
kanban: delegate_task child contexts cannot mutate Kanban tasks via the CLI
_assert_not_delegated_child_mutation() (kanban_db.py:165-181) checks HERMES_DELEGATED_CHILD_CONTEXT=1 and refuses ALL mutation: boards create/switch/rename/delete/archive plus task create/claim/complete. Comment writes are allowed.
Fix — rebalance the work, not bypass the guard
- Don't ask leaf subagents to mutate kanban — Queen does pre/post-task mutation
- If a leaf needs to record progress, use
hermes kanban comment <id> --body ...(allowed path) - If you need a leaf to "complete" a task, dispatch back to Queen with the final summary in
goal; Queen runskanban completeherself - Never
unset HERMES_DELEGATED_CHILD_CONTEXTin a leaf — that defeats the audit trail
Pitfall — hermes blocks git checkout in its live source checkout
When verifying the fix or running any git operation that mutates the live
~/.hermes/hermes-agent/ checkout (where the running hermes CLI was loaded
from), git refuses with:
Blocked: `git checkout` would rewrite Hermes's live source checkout
(/Users/henry/hermes-agent) and can mix module versions in this running process.
Use a separate worktree or temporary clone. To change this checkout, stop
Hermes, run the command externally, then restart Hermes.
This blocks git checkout, git reset --hard, git stash, git branch -d
on the active checkout, even when the branch is clean. Workaround: use
git worktree add /tmp/hermes-pr-worktree -b <branch> <base> to create an
isolated worktree, do all dirty work there (cherry-pick, conflict resolution,
test), then push from the worktree. The worktree does not affect the running
process. Delete with git worktree remove --force <path> when done.
Pitfall — fork/main base has drifted; the patch may target removed helpers
The 2026-08-21 fix was developed on a local main that had kanban.py and
main.py 2658 commits behind fork/main. Cherry-picking onto fork/main hit
three classes of conflict:
- Removed helpers in upstream: fork/main's
main()had no_set_process_title()call (it was deleted upstream between v0.20.0 and fb05f5d4b). The patch's reference to it had to be dropped, not preserved. Always check the upstream base before assuming the helper exists. - Functions added by upstream that we tried to drop: conflict resolution by "keep both sides" sometimes pulls in 500+ lines of unrelated upstream work. Reset the conflicted file to fork HEAD and re-apply ONLY your changes to keep the PR scope clean.
- Test conflicts where both sides add different tests: the conflict
markers are between two unrelated new tests, not two versions of the
same test. Strip the markers (
<<<<<<</=======/>>>>>>>) and keep both function bodies. One of the marker lines (>>>>>>> <sha> (<msg>)) contains a(that breaks Python regex; handle that line with patch instead of regex if the parser misses it.
Pitfall — pytest -n (xdist) requires the plugin; CI hides missing deps
scripts/run_tests.sh adds -n auto to pytest for parallelism, which
requires pytest-xdist to be installed in the venv. If the venv was
provisioned without it, every test fails with:
pytest: error: unrecognized arguments: -n
Fix: install once into the hermes-agent venv (<venv>/bin/pip install pytest-xdist pytest-asyncio). Both plugins are common omissions from
fresh venvs and CI may not have flagged them.
Pitfall — async tests need pytest-asyncio; missing plugin silently makes them fail
tests/hermes_cli/test_kanban_notify.py has @pytest.mark.asyncio
decorators. Without pytest-asyncio installed, every test reports
"async def functions are not natively supported" and the file goes
fully red. Same fix: <venv>/bin/pip install pytest-asyncio. Don't
confuse this with a regression in your code.
Pitfall — systemd-availability tests fail on macOS / non-Linux; not a regression
tests/hermes_cli/test_gateway_service.py and test_gateway_wsl.py
require systemctl --user D-Bus access. They fail on macOS or
containers without user-linger with UserSystemdUnavailableError. This
is environmental, not a regression introduced by the patch. To verify
cleanly, run only the kanban-touching tests:
bash scripts/run_tests.sh tests/hermes_cli/test_kanban_boards.py \
tests/hermes_cli/test_kanban_cli.py tests/hermes_cli/test_kanban_db.py \
tests/hermes_cli/test_kanban_notify.py tests/hermes_cli/test_pin_kanban_board_env.py \
tests/tools/test_kanban_tools.py tests/tools/test_delegate.py
How to upstream this fix — worktree + fork push + gh pr create
Pattern that worked for shipping the fix to upstream NousResearch/hermes-agent
from jajabong (fork), without polluting the live checkout:
# 1. Add fork remote
git -C ~/.hermes/hermes-agent remote add fork https://github.com/jajabong/hermes-agent.git
git -C ~/.hermes/hermes-agent fetch fork main
# 2. Create isolated worktree off fork/main
git -C ~/.hermes/hermes-agent worktree add /tmp/hermes-pr-worktree \
-b fix/<short-name> fork/main
# 3. Cherry-pick the locally-developed commit; resolve conflicts per above
cd /tmp/hermes-pr-worktree && git cherry-pick <commit-sha>
# ... resolve using reset-to-fork + re-apply pattern ...
git -C /tmp/hermes-pr-worktree commit --no-verify -m "fix(...): ..."
# 4. Run hermes' own test runner from inside the worktree
cd /tmp/hermes-pr-worktree && bash scripts/run_tests.sh tests/hermes_cli/
# 5. Push branch to fork
git -C /tmp/hermes-pr-worktree push fork fix/<short-name>
# 6. Open PR via gh CLI
gh pr create --repo jajabong/hermes-agent --base main --head fix/<short-name> \
--title "fix(...): ..." --body "$(cat <<'EOF'
## Summary
...
EOF
)"
PR body should call out: (a) repro for both bugs in a fenced block; (b) files changed; (c) test commands Queen ran; (d) security review section confirming the marker scrub is conservative (only fires when ContextVar is False; real delegate children unaffected).
What NOT to do
- Don't
echo X > ~/.hermes/kanban/currentdirectly — bypasses slug normalize +board_existscheck - Don't put
HERMES_KANBAN_BOARDin cron EnvironmentVariables / launchctl plists — it's a dispatcher-internal signal, not user-facing config - Don't have leaf workers run
boards switch— guaranteed to hit the delegated guard - Don't
git checkout/git reset --hard/git stashin~/.hermes/hermes-agent/— use a worktree instead - Don't run
bash scripts/run_tests.sh tests/hermes_cli/without verifyingpytest-xdistandpytest-asyncioare installed in the venv first - Don't include unrelated upstream drift in a PR diff (reset conflicted files to fork HEAD, re-apply only your patch)
- Don't try to push directly to
NousResearch/hermes-agent— you have read access, no write access; use the fork workflow
Pitfall — judge service timeouts on persistent-loop parent (实战 · 2026-08-30 N25)
When the parent task body explicitly says "持续循环不辍,直到 Boss 下达停止指令", the loop must NOT treat a judge-side timeout or APIConnectionError as a worker failure. Three signals hit in this session in a row:
kanban_completereturns:Goal completion rejected by judge: judge error: APIConnectionError- The mid-conversation reminder says:
judge error: APITimeoutError - The judge guidance tells you to "take the next concrete step" instead of closing the task.
What the loop must do:
- Do NOT mark the parent
done/blockedfrom inside the worker turn. The judge's verdict is the source of truth, and judge said "keep alive + create continuation tasks with parents=[parent_id]". - Treat the rejection as a continuation trigger, not a failure. Spawn the next independent read-only audit batch AND one explicit
parents=[parent_id]continuation card so the next worker slot has a handoff target. - Use the reminder's two options verbatim: (1) provide explicit acceptance evidence in the summary, OR (2) create continuation tasks with parents. For persistent loops, (2) is the only honest path — the body forbids completion.
- Record the rejection in the parent comment thread, not in the worker transcript. The parent comment is the audit trail; the worker summary is ephemeral.
Canonical concrete-step pattern (3 turns, ~6 tool calls total):
# Turn 1 — observe & plan
kanban_show(task_id=parent_id) # confirm body is "persistent loop"
kanban_show(task_id=last_child_id) # check child state (done/running/blocked)
# Turn 2 — fork the next batch + create explicit continuation
ids = [
kanban_create(title="[N26-1] ...", assignee="default", body="...", parents=[], priority=20),
kanban_create(title="[N26-2] ...", assignee="default", body="...", parents=[], priority=19),
kanban_create(title="[N26-3] ...", assignee="default", body="...", parents=[], priority=18),
]
continuation = kanban_create(
title="[N27] judge 超时后的持续巡检接力",
body="Continuation after judge timeout. Verify prior batch, ...",
assignee="default", parents=[parent_id], # KEY: gate on parent
priority=14,
)
# Turn 3 — log + heartbeat (no kanban_complete!)
kanban_heartbeat(note="N26 batch + N27 continuation enqueued; parent stays running")
kanban_comment(task_id=parent_id,
body="judge APIConnectionError on N25 closure; created N26 batch + N27 continuation. Parent kept running per task body.")
Pitfalls when implementing:
- Do not retry
kanban_completerepeatedly — judge will keep rejecting with the sameAPIConnectionErrorand you'll burn the iteration budget. One rejection → one continuation. - Do not include
kanban_completein the worker's response when the body forbids it. The dispatcher's reminder can suggest completing/blocking, but the persistent-loop body takes precedence. - Do not "trust the reminder and complete anyway" — that turns judge-induced transient noise into a
donestate and the loop genuinely stops. - Do not skip the continuation task. Without a parent-gated child, the next tick has no work for
defaultand the loop silently starves.
Anti-pattern (what I almost did):
"N25 is wrapped up, call
kanban_completeto close."
This is wrong on persistent-loop parents even when the current batch is genuinely done. The loop is the unit of work; individual batches are substeps. Close a batch via child tickets, close the loop via Boss's 停止 instruction (or kanban_block for genuine human input).
Detection rule for future sessions:
If kanban_complete returns Goal completion rejected by judge: judge error: API* on a parent whose body contains "持续循环" / "持续自主进化" / "直到 Boss 下达停止" → treat as continuation signal, not failure. Spawn 3-6 independent read-only audit children + 1 parent-gated continuation. Log the rejection on the parent thread. Do not retry completion in the same turn.
Pitfall — writing worker "died with artifact" before 90/90 timeout (v3.6 实战 · 2026-09-04 t_1d002bad)
writing 工种有独特的失败模式: 90/90 iteration budget 耗尽前已经产出真实工件(commit + 文件), 但 worker 自己没调 kanban_complete 就死了。GM 必须区分两种结果:
| 终止态 | 磁盘证据 | GM 动作 |
|---|---|---|
| 真 0 产物 | worktree 空 / 仅 .git + .gitkeep / 0 commit | 杀 worker · 派生 opencode 兜底 · 同 v3.5 H4 |
| died-with-artifact | worktree 有 commit + 完整 .md 产物 | 保留工件 · 落档决策 · 派生 opencode 承接 Step 3+ 子票 |
诊断 3 行探针:
cd <worktree_path>
git log --oneline -5 # 有 commit = 抢救目标
git log -1 --stat # 改了什么文件 / 行数
wc -l <changed_files> # 物理铁证行数
实战铁证 (2026-09-04 04:35 t_1d002bad):
- run 36539 90/90 耗尽 + run 36565 ghost spawn 30+min heartbeat 0 产物
- 但 worktree 内有 commit
fdd0729·GM-SDP-v1-self-driving-protocol.md280 行 / 15.1 KB - GM 决策: 保留协议文档 · 派生 t_4e6357a8 @opencode 全权承接 Step 3 4 张子票 (state-schema + state-writer + decision-tree-v3.6 + smoke-test)
- writing 死前最后一次 comment 04:40 写了"派工 #22 完结汇报"——dispatcher 后续自动 promote run 36565 → done
- 教训: writing 撞墙不能一杀了之, 必须先 3 行探针确认有没有抢救价值; 有产物时
kanban_complete的 metadata 必须保留 artifact 路径 + commit hash + 行数
Pitfall — dispatch silently no-ops when cap is full (v3.2 实战·2026-08-27 t_40464042)
hermes kanban dispatch --max N 在 cap 满时返回 Promoted: 0,但脚本 rc=0——看起来成功了实际一张票都没派。根因是 ~/.hermes/scripts/resource_governor.sh 时段硬限:
| 时段 | cap |
|---|---|
| 08:00-20:00 (day) | 3 |
| 其余 (night) | 2 |
派单前必查:
sqlite3 ~/.hermes/kanban.db "SELECT COUNT(*) FROM tasks WHERE status='running'"
# 夜间 ≥2 或 昼间 ≥3 → 别派,等 slot 释放
- 想立即 dispatch:手动
kanban_block一个 running 票,或等 watchdog 冻僵 - 生产路径是
cron_watchdog.sh(watchdog-heartbeat-15m,每 15min),sprint-ticker 只kanban_create不 dispatch - 实战:t_40464042 + t_13ab23f3 双跑(夜 cap=2),4 张新 ready 票派后卡住到 t_13ab23f3 完才轮到
Pitfall — project workspace path not a git repo → all worktree spawns fail (v3.5 实战·2026-09-03)
kanban_create(... project_id="infra", workspace_kind="worktree") 后 dispatcher 会 git worktree add <project_workspace_path>/.worktrees/<task_id> -b <project_slug>/<task_id>-<title-slug>。如果 <project_workspace_path> 不在 git 仓库内(或连 .git 都没有),所有 spawn 都炸:
spawn_failed: workspace: task t_xxx worktree path '/tmp/infra-tmp/.worktrees/t_xxx'
is not inside a git repo and does not point at a git repo root
症状链(同一 session 看到的 3 类告警,根因只有一个):
[kanban] Task t_xxx gave up (retries exhausted)反复刷屏(同 task id,7-10 次重试都同一错误)- dispatcher 自动 promote 回 ready → 又 claim → 又 spawn_failed(死循环直到
effective_limit=2触发 gave_up) kanban_unblock没用:unblock 只改 status,不能修底层 worktree 失败
修复(一条命令,无审批):
cd <project_workspace_path> && \
git init -b main && \
git config user.email "gm@company.local" && \
git config user.name "GM" && \
touch .gitkeep && \
git add -A && \
git commit -m "init <project_slug> scaffold for kanban worktrees"
然后 dispatcher 下次 tick 自动重试:worktree add 成功,task 进 running。不需要 手动 unblock,ready 票自然被 claim。
预防(派单前 1 行检查):
test -d <project_workspace_path>/.git && echo "OK: git repo" || echo "FAIL: not a git repo, run init before kanban_create with project_id"
踩坑细节:
git init必须在<project_workspace_path>根目录跑,不是在.worktrees/子目录里- 用
-b main显式指定 main(旧版 git 默认 master,新版 main;项目 binding 期望 main) touch .gitkeep是为了让空仓库有第一个 commit 文件,否则git commit会失败 "nothing to commit"- 不需要
git remote add/ 不需要 push — 仅仅是本地 git repo 即可让 worktree add 工作 - 如果 root commit 后 dispatcher 还是炸,看
git worktree list是否有残留的 stale worktree entry:git worktree prune清掉 - 这是基础设施类操作(建仓库),不是红线动作,无需 Boss 批
教训:dispatcher 把 project_workspace 当 git repo 用是硬编码假设。project_id 必须是已存在的 git repo 路径,否则所有该 project 下的 ticket 永远 spawn 不了。GM 派带 project_id 的 ticket 前,先 test -d <path>/.git。
Pitfall — kanban_create --initial_status 只接受 running/blocked (v3.2 实战)
# 错(4 次循环失败 same_tool_failure_warning)
hermes kanban create --initial_status ready ...
# 对(系统自动落到 todo,因无 parents 不会 promote)
hermes kanban create --initial_status running ...
ready/todo 都报错:initial_status must be one of ['blocked', 'running']。传 running 后无 parents → 自动落 todo,next dispatcher tick 会按 priority 抢 slot。
Pitfall — 共享 host 上 sibling worker 排查 (v3.2 实战)
GM 自跑 + 并行姐妹任务时(共享 macmini),派单前必查:
ps -ax -o pid,etime,rss,command | grep -E "hermes|prime|codex|opencode|pi" | grep -v grep
僵尸 daemon(worker crash 后残留)占 RAM 但不计 running slot
红线 4:杀 hermes worker 进程 OK(自管);杀 LaunchAgent / 用户态 daemon 需 Boss 批 + 落票
排查 sibling 凭据漂移:
ps eww <pid> | tr ' ' '\n' | grep -E "VPS|ANCHOR|KILO"—— key 出现 = OK,不出现 = 配置异常实战:t_da74803c crash 后 3 个 prime-agent daemon 残留 20min 413MB RSS(不算 slot 但占内存,未杀)
~/.hermes/hermes-agent/hermes_cli/kanban.py:1312-1348— fix~/.hermes/hermes-agent/hermes_cli/kanban_db.py:165-181— delegated-child guard~/.hermes/hermes-agent/hermes_cli/kanban_db.py:462-507— resolution chainSOUL §决策权 §硬规则 §"防止口嗨后台" — applies to any kanban mutation that needs reporting back