Skip to content

Commit a8fc5d5

Browse files
shepherdjerredCI Bot
andauthored
docs(root): define durable agent PR workflow (#1579)
* docs(root): define durable agent PR workflow * docs(root): record agent workflow PR * docs(root): record agent config audit * docs(root): record Codex hook cleanup * fix(root): restore scoped local agent hooks --------- Co-authored-by: CI Bot <ci@sjer.red>
1 parent 73c7c3b commit a8fc5d5

9 files changed

Lines changed: 117 additions & 97 deletions

File tree

.claude/hooks/trust-mise.sh

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
11
#!/usr/bin/env bash
2-
# Claude Code SessionStart hook: trust mise configs for the session's working
3-
# directory so `mise` can parse them without a manual `mise trust`. This matters
4-
# most for freshly created git worktrees, where none of the repo's mise configs
5-
# are trusted yet (mise keys trust by absolute path, so each new worktree path
6-
# is untrusted even when the main checkout's configs are already trusted).
7-
#
8-
# SessionStart runs on every session, so this hook stays cheap in the common
9-
# case (main checkout) and only does the full nested-config walk inside a linked
10-
# worktree. Trusting is best-effort: a failure must never block the session, and
11-
# plain stdout is fine here (the harness adds it to context).
2+
# Trust mise configs only for a local Claude Code session. Cloud sessions use a
3+
# managed environment and must not mutate its trust store.
124
set -euo pipefail
135

14-
# The harness may invoke hooks with a minimal PATH; include the common mise
15-
# install locations so `mise` resolves even when the login profile isn't sourced.
16-
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
6+
[ -z "${CLAUDE_CODE_REMOTE:-}" ] || exit 0
177

8+
export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
189
command -v mise >/dev/null 2>&1 || exit 0
1910

20-
# SessionStart input JSON arrives on stdin (cat on empty/closed stdin returns 0).
2111
input="$(cat)"
22-
23-
# Resolve the working dir: prefer the hook's cwd, then harness env, then PWD.
2412
dir=""
2513
if [ -n "$input" ] && command -v jq >/dev/null 2>&1; then
2614
dir="$(printf '%s' "$input" | jq -r '.cwd // empty')"
@@ -29,39 +17,22 @@ fi
2917
[ -d "$dir" ] || exit 0
3018

3119
cd "$dir"
32-
33-
# Best-effort from here: never let a trust failure abort the session.
3420
set +e
35-
36-
# Trust the working-dir config (and any parent configs) — one cheap call.
3721
mise trust --yes --quiet --all >/dev/null
3822

39-
# Only walk nested per-package configs inside a linked worktree. In the main
40-
# checkout the nested configs are almost always already trusted from earlier
41-
# sessions (trust persists per absolute path), so re-walking 70+ configs on
42-
# every session start would be wasted work. A linked worktree has an absolute
43-
# git-dir distinct from the common git-dir.
23+
# Only walk nested per-package mise.toml files inside a linked worktree — trust
24+
# persists per absolute path, so a fresh worktree path starts fully untrusted
25+
# even when the main checkout's configs are already trusted.
4426
git_dir=""
4527
common_dir=""
4628
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
4729
git_dir="$(git rev-parse --absolute-git-dir)"
4830
common_dir="$(git rev-parse --path-format=absolute --git-common-dir)"
4931
fi
50-
51-
count=0
5232
if [ -n "$git_dir" ] && [ "$git_dir" != "$common_dir" ]; then
5333
while IFS= read -r cfg; do
54-
# Count only successful trusts so the status line can't overstate coverage
55-
# when a per-package `mise trust` fails silently under `set +e`.
56-
if mise trust --yes --quiet "$cfg" >/dev/null; then
57-
count=$((count + 1))
58-
fi
34+
mise trust --yes --quiet "$cfg" >/dev/null
5935
done < <(find "$dir" \
6036
\( -name node_modules -o -name .git -o -name archive -o -name dist -o -name build -o -name target \) -prune \
6137
-o -type f \( -name mise.toml -o -name .mise.toml \) -print)
62-
echo "Trusted mise configs in worktree $dir ($count nested)"
63-
else
64-
echo "Trusted mise configs in $dir"
6538
fi
66-
67-
exit 0

.claude/hooks/worktree-reminder.sh

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
#!/usr/bin/env bash
2-
# SessionStart hook: when a session starts in the MAIN checkout, nudge the agent
3-
# to create a git worktree before editing. Pure reminder — it never blocks and
4-
# always exits 0. The text mirrors the bright-line rule in AGENTS.md
5-
# ("Parallel Work — Use Worktrees"); keep the two in sync.
6-
#
7-
# Output formats differ by tool:
8-
# - Claude Code injects a hook's plain stdout into the session context, so the
9-
# default path just prints the reminder (same as the sibling trust-mise.sh).
10-
# - Codex consumes a JSON envelope (hookSpecificOutput.additionalContext), so
11-
# its hook passes --tool=codex to select that format.
2+
# Nudge local CLI sessions in the main checkout. Hosted Claude and Codex
3+
# environments exit before inspecting or changing their managed filesystem.
124
set -euo pipefail
135

146
tool="claude"
@@ -18,71 +10,55 @@ for arg in "$@"; do
1810
esac
1911
done
2012

21-
# SessionStart input JSON arrives on stdin (cat on empty/closed stdin returns 0).
22-
input="$(cat)"
13+
if [ "$tool" = "claude" ] && [ -n "${CLAUDE_CODE_REMOTE:-}" ]; then
14+
exit 0
15+
fi
16+
if [ "$tool" = "codex" ] && { [ -n "${CODEX_CI:-}" ] || [ -n "${CODEX_CLOUD_TASKS_BASE_URL:-}" ]; }; then
17+
exit 0
18+
fi
2319

24-
# Resolve source + working dir from the payload (jq if available), else fall back.
20+
input="$(cat)"
2521
src=""
2622
dir=""
2723
if [ -n "$input" ] && command -v jq >/dev/null 2>&1; then
2824
src="$(printf '%s' "$input" | jq -r '.source // empty')"
2925
dir="$(printf '%s' "$input" | jq -r '.cwd // empty')"
3026
elif [ -n "$input" ]; then
31-
# jq unavailable — can't classify source, so skip the nudge (safe default).
3227
exit 0
3328
fi
3429
[ -n "$dir" ] || dir="${CLAUDE_PROJECT_DIR:-$PWD}"
3530
[ -d "$dir" ] || exit 0
3631

37-
# Only nudge on a genuinely new session — not on resume/compact — to avoid nagging.
3832
case "$src" in
3933
resume | compact) exit 0 ;;
4034
esac
4135

4236
cd "$dir"
43-
44-
# Nothing to nudge about outside a git work tree.
4537
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
46-
47-
# A linked worktree has an absolute git-dir distinct from the common git-dir.
48-
# If we're already in one, the agent is isolated — stay silent.
4938
git_dir="$(git rev-parse --absolute-git-dir)"
5039
common_dir="$(git rev-parse --path-format=absolute --git-common-dir)"
51-
if [ "$git_dir" != "$common_dir" ]; then
52-
exit 0
53-
fi
40+
[ "$git_dir" = "$common_dir" ] || exit 0
5441

55-
# In the main checkout: emit the reminder.
5642
message="$(cat <<'EOF'
57-
Worktree reminderthis session is in the MAIN checkout.
43+
Worktree reminder: this session is in the main checkout.
5844
59-
Before your FIRST edit on any non-trivial change — anything you'll open a PR for,
60-
anything touching more than one file, or any multi-step task — create a worktree:
45+
Before your first edit on a non-trivial change, create a worktree:
6146
6247
git worktree add .claude/worktrees/<slug> -b feature/<slug> origin/main
6348
cd .claude/worktrees/<slug>
64-
mise trust -y --all # fresh worktree configs are untrusted
65-
# Then set up deps scoped to the package(s) you'll touch — see AGENTS.md
66-
# "Development Setup" (build shared file: producers, then bun install + codegen).
49+
mise trust -y --all
6750
6851
The worktree holds a git-spice *stack* — every feature PR is a stacked PR. Manage
6952
branches and PRs with git-spice (`gs`) and load the `git-spice-helper` skill before
7053
any branch/PR op. (In scripts, `gs` is Ghostscript — call `git-spice`.)
7154
72-
Only stay in the main checkout for a single-file, single-commit fix you won't PR.
73-
When unsure, make the worktree. (Tip: `claude -w <slug>` does this at launch.)
55+
Only stay in main for a single-file, single-commit fix you will not put in a PR.
7456
EOF
7557
)"
7658

77-
if [ "$tool" = "codex" ]; then
78-
if command -v jq >/dev/null 2>&1; then
79-
jq -n --arg ctx "$message" \
80-
'{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $ctx}}'
81-
else
82-
printf '%s\n' "$message"
83-
fi
59+
if [ "$tool" = "codex" ] && command -v jq >/dev/null 2>&1; then
60+
jq -n --arg ctx "$message" \
61+
'{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $ctx}}'
8462
else
8563
printf '%s\n' "$message"
8664
fi
87-
88-
exit 0

.claude/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
{
88
"type": "command",
99
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/trust-mise.sh\"",
10+
"async": true,
1011
"statusMessage": "Trusting mise configs"
1112
},
1213
{
1314
"type": "command",
1415
"command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/worktree-reminder.sh\"",
16+
"async": true,
1517
"statusMessage": "Worktree reminder"
1618
}
1719
]

.codex/config.toml

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
# Repo-scoped Codex config. Minimal on purpose: it ONLY registers the
2-
# SessionStart worktree-reminder hook (the same script Claude Code's hook runs).
3-
#
4-
# This project layer merges on top of ~/.codex/config.toml, so do NOT set model,
5-
# sandbox, or approval here — that would override the user's global settings.
6-
#
7-
# Loading project-local hooks requires this .codex/ layer to be trusted; Codex
8-
# prompts once per machine. The `hooks` feature is stable/enabled by default.
9-
# The hook only nudges when a session opens in the MAIN checkout — see
10-
# .claude/hooks/worktree-reminder.sh (shared with Claude Code).
11-
1+
# Repository-local Codex CLI hook. The shared script suppresses itself in hosted
2+
# Codex task environments, so cloud sessions never run worktree setup logic.
123
[[hooks.SessionStart]]
134

145
[[hooks.SessionStart.hooks]]
156
type = "command"
167
command = 'bash "$(git rev-parse --show-toplevel)/.claude/hooks/worktree-reminder.sh" --tool=codex'
8+
async = true
179
timeout = 10
1810
statusMessage = "Worktree reminder"

.codex/environments/environment.toml

Lines changed: 0 additions & 6 deletions
This file was deleted.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ packages/dpp/
4545
!/.claude/hooks/
4646
/.claude/hooks/*
4747
!/.claude/hooks/trust-mise.sh
48-
!/.claude/hooks/install-git-hooks.sh
4948
!/.claude/hooks/worktree-reminder.sh
5049
/.claude.json
5150
/.claude.json.*
5251

52+
/.codex/*
53+
!/.codex/config.toml
54+
5355
# Clauderon daemon data (root level only)
5456
/.clauderon/
5557

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// OpenCode runs locally. The detached queries keep session creation non-blocking.
2+
export const WorktreeReminder = async ({ client, directory, worktree }) => ({
3+
event: async ({ event }) => {
4+
if (event.type !== "session.created") return;
5+
6+
const gitDir = Bun.spawn(["git", "rev-parse", "--absolute-git-dir"], {
7+
cwd: worktree,
8+
stdout: "pipe",
9+
stderr: "ignore",
10+
});
11+
const commonDir = Bun.spawn(
12+
["git", "rev-parse", "--path-format=absolute", "--git-common-dir"],
13+
{ cwd: worktree, stdout: "pipe", stderr: "ignore" },
14+
);
15+
16+
void Promise.all([
17+
new Response(gitDir.stdout).text(),
18+
new Response(commonDir.stdout).text(),
19+
gitDir.exited,
20+
commonDir.exited,
21+
]).then(
22+
([gitDirPath, commonDirPath, gitDirExitCode, commonDirExitCode]) => {
23+
if (
24+
gitDirExitCode !== 0 ||
25+
commonDirExitCode !== 0 ||
26+
gitDirPath.trim() !== commonDirPath.trim()
27+
) {
28+
return;
29+
}
30+
31+
void client.tui.showToast({
32+
body: {
33+
title: "Worktree reminder",
34+
message:
35+
"This session is in the main checkout. Create a worktree before a non-trivial edit.",
36+
variant: "warning",
37+
duration: 10_000,
38+
},
39+
query: { directory },
40+
});
41+
},
42+
);
43+
},
44+
});

AGENTS.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ sandbox/ # Personal scratch (not shipped, excluded from m
8080

8181
**Rule of thumb:** if the design itself is the artifact, it's a plan. If you just need a journal of what happened, it's a log.
8282

83+
### Session location and durable context
84+
85+
- Start every session by creating its log or plan in the main checkout under `packages/docs/`, before creating a worktree.
86+
- If the task moves to a worktree, move every agent-created write, including the session log or plan, into that worktree immediately. Do not leave duplicate or partial agent work in the main checkout.
87+
- The primary artifact for a code-changing session is a pull request. Create a draft PR from the worktree as soon as it contains a coherent first commit, and promote it to ready for review only after verification is complete.
88+
- Assume the chat may end immediately after the draft or final PR is created. Record unfinished work and handoff context in `packages/docs/`, the PR description, or an explicit final response to the user.
89+
- These instructions apply to all agents. Repository lifecycle hooks are scoped to local CLI runtimes and must exit immediately in hosted or web environments.
90+
8391
### Mirroring harness plans
8492

8593
When plan mode is used, copy the approved plan from `~/.claude/plans/<slug>.md` into `packages/docs/plans/` using the dated naming convention before beginning implementation.
@@ -298,7 +306,7 @@ files a build needs.
298306

299307
After PR merge: run `git-spice repo sync` to delete merged branches and retarget the rest of the stack, then `git worktree remove .claude/worktrees/<feature-slug>` and `git branch -d feature/<slug>` from the main checkout. Run `git worktree prune` to clean up stale entries.
300308

301-
See the `worktree-workflow` skill for the full workflow. `claude -w <slug>` creates and enters a worktree at launch; for Codex, create the worktree first and start it with `codex -C <dir>`. A `SessionStart` hook (`.claude/hooks/worktree-reminder.sh`, wired for both Claude Code and Codex) also reminds you whenever a session opens in the main checkout.
309+
See the `worktree-workflow` skill for the full workflow. `claude -w <slug>` creates and enters a worktree at launch; for Codex, create the worktree first and start it with `codex -C <dir>`.
302310

303311
**If you were started in a worktree, stay in that worktree.** Keep every command, search, and file operation scoped to the worktree path you were launched in. Do not `cd` into, read from, or write to the main checkout (the parent of the `.claude/worktrees/` directory you are in) — the worktree is a complete checkout with the same files, so there is no reason to reach outside it. The main checkout may hold the user's own in-progress work; only touch it when the user explicitly asks.
304312

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
id: nonblocking-agent-hooks
3+
type: log
4+
status: complete
5+
board: false
6+
---
7+
8+
# Durable Agent Sessions
9+
10+
Define a durable session and pull-request workflow for repository agents without executing lifecycle hooks in cloud or web sessions.
11+
12+
## Session Log - 2026-07-19
13+
14+
### Done
15+
16+
- Added the main-checkout log, worktree handoff, durable-context, and draft-PR policy to `AGENTS.md` and `CLAUDE.md`.
17+
- Restored non-blocking repository-local Claude Code, Codex, and OpenCode lifecycle hooks. Claude hooks skip `CLAUDE_CODE_REMOTE`; Codex hooks skip hosted-task environment markers.
18+
- Updated `.gitignore` to ignore local `.claude/` and `.codex/` state.
19+
- Opened draft PR #1579: `https://github.com/shepherdjerred/monorepo/pull/1579`.
20+
- Audited user and repository configuration for Claude Code, Codex, and OpenCode. Claude and OpenCode user configuration is chezmoi-managed; Codex user configuration is unmanaged.
21+
- Removed stale Codex hook-trust records from the unmanaged `~/.codex/config.toml`.
22+
23+
### Remaining
24+
25+
- None.
26+
27+
### Caveats
28+
29+
- The durable workflow is prompt-based for all agents, with supplemental lifecycle hooks only for local CLI runtimes.
30+
- PR #1579 remains draft pending workflow review.
31+
- User-level instructions are intentionally cross-repository only. The monorepo-specific session, worktree, log, and PR policy belongs in repository `AGENTS.md`.

0 commit comments

Comments
 (0)