Skip to content

feat(trust): share folder trust as it's given and resume herdr panes on their seat - #18

Merged
gelzinn merged 2 commits into
mainfrom
feat/session-hook
Oct 28, 2026
Merged

gelzinn merged 2 commits into
mainfrom
feat/session-hook

Conversation

@gelzinn

@gelzinn gelzinn commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Description

Trusting a folder in one Claude seat only reached the other seats on seats sync or activation, so opening another seat showed Claude Code's trust prompt again. And herdr restored Claude Code and Codex panes with claude --resume <id> / codex resume <id>, which open the active seat (~/.claude, ~/.codex) instead of the one the conversation ran on.

  • Push when trust is given. seats hook session runs from a SessionStart and a Stop hook (Stop because .claude.json may not be written yet at SessionStart) and merges the current seat's trust into the other seats. It never rewrites the current seat's .claude.json, and skips seats that keep their own trust and seats with Claude open (by CLAUDE_CONFIG_DIR, or plain claude on ~/.claude). It prints nothing, reads and ignores stdin, and always exits 0. The registered command is command -v seats >/dev/null 2>&1 && seats hook session || true, so a Claude that can't see seats on its PATH never shows a hook error.
  • Pull on launch. supervise() merges the other seats' trust into the seat just before each spawn, which covers seats run, the picker, seats move, auto-switch and herdr restores. Seats skipped by the hook catch up there. It never fails a launch.
  • Hook registration. seats sync, activation and seats add add both entries to the shared Claude settings.json (created and linked when no seat has one), next to existing hooks, recognised by their command so they're never duplicated; --dry-run reports it. The limit hook stays in --settings for supervised launches only. A seat that excludes settings.json doesn't get the hook (documented).
  • seats exclude/include <seat> trust. A seat that keeps its own trust neither gives nor gets the per-folder half of .claude.json (trust flags, allowedTools, .mcp.json approvals), on sync, launch or from the hook; user MCP servers still merge. Stored as ownTrust in config.json. Exclusion is per seat, not per folder. Only for Claude; other tools get an error pointing at their shared trust file.
  • mergeSeats takes options: keptOut, ownTrust and targets (reads every seat, writes only the targets).
  • herdr plugin (plugins/herdr). On pane.agent_detected and pane.agent_status_changed it runs seats hook herdr, which reads the pane's session id, finds the seat that has the conversation (newest copy wins, e.g. after seats move or auto-switch) and runs herdr pane report-agent-session <pane> --source seats --agent <claude|codex> -- seats run <tool>:<seat> <resume args>. It's a plugin reporting a resume command because herdr's herdr:claude and herdr:codex integrations are session-only: a report from source seats is accepted once herdr sees the agent in the pane, survives later session reports, and takes precedence at restore. Already-reported commands aren't sent twice (state under HERDR_PLUGIN_STATE_DIR).
    • Claude Code: projects/*/<id>.jsonl → seats run claude:<seat> --resume <id>.
    • Codex: sessions/YYYY/MM/DD/rollout-<time>-<id>.jsonl or archived_sessions/ (id matched exactly from the file name) → seats run codex:<seat> resume <id>. SeatSource.sessions.list is now optional, and Codex only has find, so seats sessions and seats move stay Claude-only (copying a Codex conversation to another account isn't confirmed to resume).
  • Resuming on a seat at its limit. With auto-switch on, a launch that resumes a conversation (--resume <id> / resume <id>, read back by a new sessions.resumedId) on a seat already at its limit used to switch to the next seat with the same args, where that conversation doesn't exist. It now copies the conversation there first (sessions.find + copyTranscript, as the mid-session switch does). When it can't (not found, or a source without failover, i.e. Codex), it stays on the named seat and says it's at its limit. This matters for the herdr plugin and seats move's "open now", which both launch seats run <tool>:<seat> --resume <id>.
  • integrations/ renamed to plugins/, so plugins/paseo and plugins/herdr live together. Paseo users linked to integrations/paseo must update the path.
  • Checked against a real herdr 0.9.5 in an isolated seats-e2e session with a throwaway HOME, once per tool: a fake claude / codex in the pane was detected, the session was reported as herdr:claude / herdr:codex would, seats hook herdr ran, and the saved session.json held agent_resume: {source: "seats", argv: ["seats","run","claude:b","--resume","<uuid>"]} and {source: "seats", argv: ["seats","run","codex:b","resume","<uuid>"]}.
  • README, help text and tests updated.

User-facing changes

BEGIN_COMMIT_OVERRIDE
feat(trust): a folder trusted in one Claude seat is trusted in every seat right away, with seats exclude <seat> trust for a seat that keeps its own
feat(herdr): a herdr plugin reopens restored Claude Code and Codex panes on the seat their conversation ran on
END_COMMIT_OVERRIDE

Type of change

  • Bug fix
  • New provider
  • New feature
  • Docs / metadata

Checklist

  • bun run check passes (Biome lint and format, typecheck, tests)
  • bun src/cli.ts help runs without errors
  • Changes to seat folders were tried with --dry-run or a throwaway HOME
  • A new provider (if added) lives in src/providers/<id>/ and is listed in src/providers/index.ts

…on their seat

Trusting a folder in one Claude seat no longer waits for seats sync. A
SessionStart and Stop hook (seats hook session), added to the shared
settings.json by sync, switching and seats add, carries it to the other
seats right away, and every launch through seats pulls the other seats'
trust into the seat first. Seats with Claude open are left for their
next launch. seats exclude <seat> trust lets a seat keep its own.

The new herdr plugin in plugins/herdr makes herdr reopen a Claude pane
after a restart with seats run claude:<seat> --resume <id>, on the seat
that has the conversation, instead of the active one. The Paseo plugin
moved from integrations/paseo to plugins/paseo.
@gelzinn gelzinn self-assigned this Oct 28, 2026
The herdr plugin now covers Codex as well as Claude Code. A restored
Codex pane reopens with seats run codex:<seat> resume <id> on the seat
whose sessions/ or archived_sessions/ holds that rollout, instead of
codex resume on the active seat. seats only finds Codex conversations
by file name: seats sessions and seats move stay Claude-only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant