claude_hooks 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/skills/ci-monitoring/SKILL.md +103 -0
  3. data/.agents/skills/run-tests/SKILL.md +45 -0
  4. data/.claude/auto-fix.md +7 -0
  5. data/.claude/settings.local.json +21 -0
  6. data/AGENTS.md +46 -0
  7. data/CHANGELOG.md +93 -0
  8. data/README.md +159 -21
  9. data/docs/1.1.0_UPGRADE_GUIDE.md +269 -0
  10. data/docs/API/COMMON.md +7 -0
  11. data/docs/API/CONFIG_CHANGE.md +39 -0
  12. data/docs/API/CWD_CHANGED.md +34 -0
  13. data/docs/API/ELICITATION.md +44 -0
  14. data/docs/API/ELICITATION_RESULT.md +43 -0
  15. data/docs/API/FILE_CHANGED.md +37 -0
  16. data/docs/API/INSTRUCTIONS_LOADED.md +30 -0
  17. data/docs/API/MESSAGE_DISPLAY.md +38 -0
  18. data/docs/API/NOTIFICATION.md +1 -0
  19. data/docs/API/PERMISSION_DENIED.md +37 -0
  20. data/docs/API/PERMISSION_REQUEST.md +199 -0
  21. data/docs/API/POST_COMPACT.md +30 -0
  22. data/docs/API/POST_TOOL_BATCH.md +39 -0
  23. data/docs/API/POST_TOOL_USE.md +1 -0
  24. data/docs/API/POST_TOOL_USE_FAILURE.md +31 -0
  25. data/docs/API/PRE_COMPACT.md +10 -2
  26. data/docs/API/PRE_TOOL_USE.md +9 -1
  27. data/docs/API/SETUP.md +37 -0
  28. data/docs/API/STOP_FAILURE.md +19 -0
  29. data/docs/API/SUBAGENT_START.md +40 -0
  30. data/docs/API/TASK_COMPLETED.md +43 -0
  31. data/docs/API/TASK_CREATED.md +43 -0
  32. data/docs/API/TEAMMATE_IDLE.md +40 -0
  33. data/docs/API/USER_PROMPT_EXPANSION.md +43 -0
  34. data/docs/API/WORKTREE_CREATE.md +39 -0
  35. data/docs/API/WORKTREE_REMOVE.md +33 -0
  36. data/docs/PROMPT_BASED_HOOKS.md +386 -0
  37. data/docs/external/claude-hooks-reference.md +2846 -18
  38. data/docs/mitts/ideabox.md +12 -0
  39. data/docs/mitts/prd.md +417 -0
  40. data/docs/mitts/setup.md +303 -0
  41. data/docs/mitts/task.md +44 -0
  42. data/lib/claude_hooks/base.rb +30 -1
  43. data/lib/claude_hooks/config_change.rb +28 -0
  44. data/lib/claude_hooks/cwd_changed.rb +28 -0
  45. data/lib/claude_hooks/elicitation.rb +61 -0
  46. data/lib/claude_hooks/elicitation_result.rb +57 -0
  47. data/lib/claude_hooks/file_changed.rb +41 -0
  48. data/lib/claude_hooks/instructions_loaded.rb +23 -0
  49. data/lib/claude_hooks/message_display.rb +58 -0
  50. data/lib/claude_hooks/notification.rb +5 -1
  51. data/lib/claude_hooks/output/base.rb +48 -1
  52. data/lib/claude_hooks/output/config_change.rb +47 -0
  53. data/lib/claude_hooks/output/cwd_changed.rb +42 -0
  54. data/lib/claude_hooks/output/elicitation.rb +67 -0
  55. data/lib/claude_hooks/output/elicitation_result.rb +63 -0
  56. data/lib/claude_hooks/output/file_changed.rb +42 -0
  57. data/lib/claude_hooks/output/instructions_loaded.rb +19 -0
  58. data/lib/claude_hooks/output/message_display.rb +49 -0
  59. data/lib/claude_hooks/output/permission_denied.rb +42 -0
  60. data/lib/claude_hooks/output/permission_request.rb +120 -0
  61. data/lib/claude_hooks/output/post_compact.rb +18 -0
  62. data/lib/claude_hooks/output/post_tool_batch.rb +51 -0
  63. data/lib/claude_hooks/output/post_tool_use.rb +12 -0
  64. data/lib/claude_hooks/output/post_tool_use_failure.rb +42 -0
  65. data/lib/claude_hooks/output/pre_compact.rb +34 -3
  66. data/lib/claude_hooks/output/pre_tool_use.rb +26 -2
  67. data/lib/claude_hooks/output/session_start.rb +30 -8
  68. data/lib/claude_hooks/output/setup.rb +42 -0
  69. data/lib/claude_hooks/output/stop.rb +15 -0
  70. data/lib/claude_hooks/output/stop_failure.rb +19 -0
  71. data/lib/claude_hooks/output/subagent_start.rb +42 -0
  72. data/lib/claude_hooks/output/task_completed.rb +18 -0
  73. data/lib/claude_hooks/output/task_created.rb +18 -0
  74. data/lib/claude_hooks/output/teammate_idle.rb +18 -0
  75. data/lib/claude_hooks/output/user_prompt_expansion.rb +51 -0
  76. data/lib/claude_hooks/output/worktree_create.rb +60 -0
  77. data/lib/claude_hooks/output/worktree_remove.rb +19 -0
  78. data/lib/claude_hooks/permission_denied.rb +41 -0
  79. data/lib/claude_hooks/permission_request.rb +63 -0
  80. data/lib/claude_hooks/post_compact.rb +23 -0
  81. data/lib/claude_hooks/post_tool_batch.rb +59 -0
  82. data/lib/claude_hooks/post_tool_use.rb +15 -1
  83. data/lib/claude_hooks/post_tool_use_failure.rb +45 -0
  84. data/lib/claude_hooks/pre_compact.rb +7 -0
  85. data/lib/claude_hooks/pre_tool_use.rb +34 -1
  86. data/lib/claude_hooks/session_start.rb +28 -0
  87. data/lib/claude_hooks/setup.rb +25 -0
  88. data/lib/claude_hooks/stop.rb +17 -0
  89. data/lib/claude_hooks/stop_failure.rb +27 -0
  90. data/lib/claude_hooks/subagent_start.rb +23 -0
  91. data/lib/claude_hooks/subagent_stop.rb +5 -1
  92. data/lib/claude_hooks/task_completed.rb +35 -0
  93. data/lib/claude_hooks/task_created.rb +35 -0
  94. data/lib/claude_hooks/teammate_idle.rb +23 -0
  95. data/lib/claude_hooks/user_prompt_expansion.rb +45 -0
  96. data/lib/claude_hooks/version.rb +1 -1
  97. data/lib/claude_hooks/worktree_create.rb +27 -0
  98. data/lib/claude_hooks/worktree_remove.rb +19 -0
  99. data/lib/claude_hooks.rb +42 -0
  100. metadata +76 -2
@@ -0,0 +1,12 @@
1
+ ## Triage
2
+
3
+ ### Issue triage on issues: opened
4
+
5
+ We have no automatic triage. max-sixty/tend auto-labels, checks duplicates, attempts reproduction, and may even open a fix PR for obvious bugs. A simpler version would just be: classify + look for duplicates + ask clarifying questions.
6
+
7
+ ### Meta review
8
+
9
+ review-reviewers — meta-quality loop
10
+
11
+ max-sixty/tend operates a hourly job that reviews the bot's own behavior on adopter repos, accumulating evidence in a gist over a month before acting. This is an interesting quality-assurance
12
+ layer we have nothing equivalent to.
data/docs/mitts/prd.md ADDED
@@ -0,0 +1,417 @@
1
+ # Mitts Coding Bots (GitHub Actions)
2
+
3
+ This repo runs AI coding-agent "bots" entirely inside GitHub Actions. You talk
4
+ to them from issues and pull requests, and they read code, make changes, open
5
+ and update PRs, review PRs, and propose plans — all using **your own
6
+ OpenAI-compatible LLM endpoint** (no third-party SaaS coding platform, no
7
+ OpenHands Cloud subscription).
8
+
9
+ ---
10
+
11
+ ## What we're trying to achieve
12
+
13
+ Deploy a set of AI assistants that behave like a junior maintainer, driven
14
+ purely from GitHub, with these capabilities:
15
+
16
+ 1. **Discuss in an issue → open a PR** — describe work in an issue, mention the
17
+ bot, and it implements the change and opens a PR.
18
+ 2. **Follow-ups on an existing PR** — ask the bot for more changes and it pushes
19
+ commits to that PR's branch.
20
+ 3. **Discuss on a PR or issue** — ask questions and the bot replies (no code change).
21
+ 4. **Review PRs** — the bot posts a code review, automatically and on demand.
22
+ 5. **Plan / investigate** — ask `@mitts plan` and the bot proposes a
23
+ structured plan as a comment, without touching any code.
24
+
25
+ Two hard constraints shaped the design:
26
+
27
+ - **Runs only in GitHub Actions** — no separate always-on server, no hosted
28
+ agent service. The agent process lives and dies inside each Actions run.
29
+ - **Uses our own inference** — an OpenAI-compatible API key + HTTP endpoint,
30
+ not a vendor-locked coding product.
31
+
32
+ ---
33
+
34
+ ## Why OpenHands (and what we ruled out)
35
+
36
+ | Option | Runs fully in Actions | Custom OpenAI-compatible endpoint | Verdict |
37
+ |---|---|---|---|
38
+ | **OpenHands** (extensions plugins + SDK) | ✅ | ✅ explicit `llm-base-url`, routes via LiteLLM | **Chosen** |
39
+ | `anthropic/claude-code-action` | ✅ | ❌ Anthropic / Bedrock / Vertex / Foundry only | Rejected (endpoint constraint) |
40
+ | `max-sixty/tend` | ✅ | ❌ no base-URL config surface | Rejected (would need patching) |
41
+ | Aider in a workflow | ✅ | ✅ | Viable, but no built-in bot/trigger UX — more glue |
42
+
43
+ Also note the distinction that made OpenHands confusing at first:
44
+
45
+ - `OpenHands/openhands-github-action` (the repo) is a **thin cloud client** — it
46
+ needs an `OPENHANDS_API_KEY` and talks to `app.all-hands.dev`; the agent runs
47
+ on *their* infrastructure. **We do not use this.**
48
+ - The **`OpenHands/extensions` plugins** and the **`openhands-sdk`** run the
49
+ agent **inside our runner**. Because everything goes through
50
+ [LiteLLM](https://github.com/BerriAI/litellm), any OpenAI-compatible endpoint
51
+ works via `LLM_BASE_URL` + an `openai/<model>` model id. **This is what we
52
+ use.**
53
+
54
+ ---
55
+
56
+ ## How it works
57
+
58
+ ### The pieces
59
+
60
+ ```
61
+ AGENTS.md # repo guidance — loaded by all bots
62
+ .agents/
63
+ └── skills/
64
+ ├── run-tests/
65
+ │ └── SKILL.md # keyword-triggered skill (test/tests/spec)
66
+ └── ci-monitoring/
67
+ └── SKILL.md # safe CI-wait pattern (ci/checks/green)
68
+ .github/
69
+ ├── actions/
70
+ │ └── run-mitts/
71
+ │ └── action.yml # composite action: shared setup + run for the issue/PR bots
72
+ ├── mitts/
73
+ │ ├── agent_task.py # shared SDK agent runner (uses our endpoint)
74
+ │ ├── context_builder.py # assembles the full deterministic prompt (gh/git)
75
+ │ ├── recall_tool.py # custom SDK tool: recall_prior_reasoning (cross-run memory)
76
+ │ └── hooks/
77
+ │ └── block_dangerous.sh # PreToolUse guardrail (blocks force-push / CI edits)
78
+ └── workflows/
79
+ ├── mitts-pr-review.yml # review PRs (auto + "@mitts review")
80
+ ├── mitts-issue.yml # "@mitts ..." on an issue → implements (PR), answers, or plans
81
+ └── mitts-pr-followup.yml # "@mitts ..." on a PR → commits, answers, or plans
82
+ ```
83
+
84
+ The two `agent_task.py`-based bots (issue + PR follow-up) share their runner
85
+ steps — checkout, toolchain, state restore/persist, prompt build, agent run —
86
+ via the **`.github/actions/run-mitts`** composite action, so each workflow
87
+ keeps only its own triggers, guards, and comments. `pr-review` uses the external
88
+ OpenHands action instead and doesn't share this.
89
+
90
+ ### PR review — `mitts-pr-review.yml`
91
+
92
+ Uses OpenHands' official, ready-made composite action
93
+ `OpenHands/extensions/plugins/pr-review@main`. We pass it our
94
+ `llm-model` / `llm-base-url` / `llm-api-key`. It fetches the diff and any prior
95
+ review context, then posts inline review comments via the GitHub API.
96
+
97
+ Triggers: on PR `opened`/`reopened`, or when someone comments `@mitts review`
98
+ on the PR. (It intentionally does **not** re-run on every push/`synchronize` — a
99
+ review fires when the PR opens or when you explicitly ask.)
100
+
101
+ The `pr-review` action loads `AGENTS.md` (via its own `load_project_skills` call)
102
+ but does not use `agent_task.py`, so it doesn't have cross-run memory or the
103
+ guardrail hook.
104
+
105
+ ### Issue and PR bots — `agent_task.py`
106
+
107
+ There is no turnkey OpenHands Action for "issue → PR" or "PR follow-up", so these
108
+ workflows run a generic agent script built on `openhands-sdk`. The full pipeline:
109
+
110
+ ```
111
+ load_available_skills(project + optional public) # AGENTS.md, .agents/skills/
112
+ → AgentContext(skills=...)
113
+ register_builtins_agents() # code-reviewer, web-researcher, etc.
114
+ + get_default_tools(enable_sub_agents=True) # bash + file editor + task tool
115
+ + recall_prior_reasoning tool (when memory on)
116
+ → Agent(llm, tools, agent_context, condenser=...)
117
+ → HookConfig(pre_tool_use=[block_dangerous.sh]) # guardrail hook
118
+ → Conversation(agent, hook_config, secrets, persistence)
119
+ → send_message(AGENT_PROMPT); run()
120
+ → conversation_stats → $GITHUB_STEP_SUMMARY
121
+ ```
122
+
123
+ `agent_task.py` carries a **PEP 723** inline dependency header, so the workflows
124
+ invoke it with `uv run` and the SDK resolves deterministically on a clean runner
125
+ (there is no separate install step).
126
+
127
+ **Intent detection** is handled in the task templates inside `context_builder.py`.
128
+ On each run the agent sees:
129
+
130
+ - **CHANGE / IMPLEMENT** — makes code changes, runs tests, commits, pushes.
131
+ - **QUESTION / DISCUSSION** — investigates and replies as a comment, no code change.
132
+ - **PLAN** (triggered by `@mitts plan`) — investigates and posts a structured
133
+ proposed plan as a comment, no commits or branches.
134
+
135
+ ### Skills and `AGENTS.md`
136
+
137
+ `agent_task.py` calls `load_available_skills(work_dir, include_project=True)` on
138
+ startup, which picks up:
139
+
140
+ - **`AGENTS.md`** at the repo root — always-loaded static guidance (conventions,
141
+ test commands, policies).
142
+ - **`.agents/skills/`** directories following the
143
+ [AgentSkills](https://agentskills.io/specification) format. Each `SKILL.md` has
144
+ YAML frontmatter with `triggers:` keywords; the agent sees a brief description of
145
+ every skill and its full content is injected on demand when a trigger word appears.
146
+ Example: `.agents/skills/run-tests/` triggers on `test`/`tests`/`spec` and
147
+ provides the exact test-run commands.
148
+ - Optionally, **public skills** from `OpenHands/extensions` when
149
+ `OPENHANDS_LOAD_PUBLIC_SKILLS=1` is set (see env knobs below).
150
+
151
+ Skills give the agent persistent, progressive-disclosure repo knowledge without
152
+ bloating every prompt.
153
+
154
+ ### Sub-agent delegation
155
+
156
+ `agent_task.py` registers the built-in sub-agents (`code-reviewer`,
157
+ `web-researcher`, etc.) and enables `TaskToolSet` so the main agent can delegate
158
+ complex sub-tasks. For example, it can spin up a `code-reviewer` sub-agent to
159
+ do a thorough review before writing the PR description, then get back a structured
160
+ result.
161
+
162
+ ### Guardrail hooks
163
+
164
+ A `PreToolUse` shell hook (`block_dangerous.sh`) runs before every terminal
165
+ command the agent executes. It blocks:
166
+
167
+ - **Force-push** (`git push --force` / `-f`) — automated agents must never
168
+ rewrite history.
169
+ - **Writes to `.github/workflows/`** — CI configuration must be changed by a human.
170
+ - **Writes to secret/credential files** (`.env`, `.envrc`, `*secret*`, etc.).
171
+
172
+ Exit code `2` denies the command and surfaces the reason to the agent as an
173
+ error observation. This makes the prompt-level rules deterministic rather than
174
+ advisory.
175
+
176
+ The hook is **behavioral defense-in-depth**, not a boundary: it pattern-matches
177
+ command strings, which a determined (or prompt-injected) agent could route around
178
+ (e.g. shelling out from a Ruby script, encoding the command). It stops the obvious
179
+ mistakes; it does not *guarantee* the agent can't do damage. For the operations
180
+ that must never happen — landing code on `main`, publishing a release — the
181
+ guarantee has to be **structural** (see below).
182
+
183
+ ### Structural containment (write, not admin)
184
+
185
+ The agent runs with `contents: write` + `pull-requests: write`. The security
186
+ model is that **every privileged path chains back to an operation the bot cannot
187
+ perform**, so even a fully prompt-injected agent is contained — independent of the
188
+ behavioral hook. Two things must be locked down at the repo level (GitHub Settings,
189
+ one-time, done by a human):
190
+
191
+ 1. **Protect `main` so the bot can't merge.** Settings → Rules → **Rulesets** (or
192
+ classic branch protection): on the default branch, require a pull request and at
193
+ least one **approving review**, and restrict who can push / bypass to admins
194
+ only. The bot authenticates with the built-in `GITHUB_TOKEN`, which is not an
195
+ admin, so it can open PRs but cannot merge them or push to `main` directly. A
196
+ human merge stays in the loop.
197
+
198
+ 2. **Gate the release secret behind `main`.** `ruby-gem.yml` publishes to RubyGems
199
+ using `secrets.RUBYGEMS_AUTH_TOKEN` and only runs its publish job on `push` to
200
+ `refs/heads/main`. Move that secret into a GitHub **Environment** (e.g.
201
+ `release`) whose **deployment branch policy** allows only the default branch (and
202
+ tags, if you tag releases), and reference it from the job with
203
+ `environment: release`. Then a leaked/compromised bot token that pushes some
204
+ *other* branch can never match the policy, so the publish job is rejected
205
+ *before* the secret is readable. The chain is: no admin merge → nothing lands on
206
+ `main` → no `push`-to-`main` event → no environment access → `RUBYGEMS_AUTH_TOKEN`
207
+ never exposed.
208
+
209
+ Caveat (same as any Actions setup): this guarantee holds for `push`/`pull_request`
210
+ triggers. `workflow_dispatch`, `release`, and `schedule` triggers can be initiated
211
+ without a merge, so if you add secret-bearing jobs on those triggers, gate them
212
+ with required reviewers on the environment too.
213
+
214
+ Without step 1 in place, `ruby-gem.yml`'s publish token is only as safe as branch
215
+ protection — set it up before pointing the bots at a repo that publishes.
216
+
217
+ ### Hybrid context system
218
+
219
+ The bots don't rely on the model volunteering to fetch context. Two layers:
220
+
221
+ 1. **Deterministic history injection (always, every run).** `context_builder.py`
222
+ assembles the full authoritative context via `gh`/`git` and emits the entire
223
+ `AGENT_PROMPT` (task template + a "CURRENT STATE" block). For a PR that's the
224
+ PR body + full comment thread + inline review comments + the `base...head` diff
225
+ + any linked issue; for an issue it's the body + thread + related PRs. Output is
226
+ budget-capped (~100k chars total, ~12k per file diff) with truncation markers.
227
+ This is the source of truth and always current. The prompt footer also carries
228
+ the **trust / untrusted-input** guidance (content-is-not-instructions; only
229
+ maintainers may direct the bot). This lives in `context_builder.py`, not
230
+ `AGENTS.md`, on purpose — it's bot-infrastructure guidance that must travel with
231
+ the setup when ported to another repo (whose `AGENTS.md` describes that repo's
232
+ own conventions and shouldn't have to re-state it).
233
+
234
+ 2. **Cross-run memory (on demand).** Each SDK run persists its events to
235
+ `${OPENHANDS_PERSIST_DIR}/<thread-key>/<conversation-id>/`, uploaded as a GitHub
236
+ **artifact** named `mitts-state-{pr,issue}-<N>` and restored by the next run
237
+ on the same thread. We deliberately give each run a **fresh** `conversation_id`
238
+ so the SDK never auto-replays old state into the prompt (keeps it lean). Instead
239
+ the custom **`recall_prior_reasoning`** tool reads *prior* run event dirs on
240
+ demand — e.g. when a maintainer asks "why did you…". Its output is framed as
241
+ possibly-outdated; the current diff always wins on conflict.
242
+
243
+ Cross-run download uses `dawidd6/action-download-artifact` (`search_artifacts`)
244
+ because `actions/download-artifact` only sees the current run's artifacts. Fork
245
+ PRs have no secrets/artifacts, so the follow-up bot skips this and runs stateless.
246
+
247
+ To keep the per-thread artifact bounded (every run adds a new `<conversation-id>/`
248
+ subdir and the whole tree is re-uploaded), `agent_task.py` prunes to the most
249
+ recent `OPENHANDS_KEEP_RUNS` runs (default 5) before each run.
250
+
251
+ **PR review is the exception:** the ready-made `pr-review` action runs its own
252
+ script, so we can't inject the assembled prompt or the recall tool into it. It
253
+ already fetches the diff + prior review context itself, and it loads `AGENTS.md`
254
+ (via `load_project_skills`) for static guidance. So PR review participates in the
255
+ *static* half of the system (`AGENTS.md`) only — no per-run injection, no
256
+ cross-run memory.
257
+
258
+ ### Cost reporting
259
+
260
+ After every run, `agent_task.py` reads `conversation.conversation_stats` and
261
+ appends a brief table (model, cost, prompt tokens, completion tokens, skills
262
+ loaded) to the GitHub Actions **step summary**. Open the run in the Actions UI and
263
+ click the summary tab to see it.
264
+
265
+ If a run ends with status `ERROR` or `STUCK` (stuck detection is on by default),
266
+ the script exits non-zero so the existing `if: failure()` workflow step posts a
267
+ failure comment on the issue/PR.
268
+
269
+ ---
270
+
271
+ ## Setup
272
+
273
+ ### 1. Configure the endpoint
274
+
275
+ Non-secret config goes in **Actions variables**; the key goes in an **Actions
276
+ secret**:
277
+
278
+ ```bash
279
+ cd /path/to/claude_hooks
280
+
281
+ gh variable set OPENHANDS_LLM_MODEL --body "openai/<your-model-name>"
282
+ gh variable set OPENHANDS_LLM_BASE_URL --body "https://<your-endpoint>/v1"
283
+ gh secret set OPENHANDS_LLM_API_KEY --body "<your-api-key>"
284
+ ```
285
+
286
+ - Use the `openai/` prefix so LiteLLM treats it as an OpenAI-compatible backend
287
+ (e.g. `openai/gpt-oss-120b`).
288
+ - The base URL usually needs the `/v1` suffix — confirm with your provider.
289
+ - Do **not** create `GITHUB_TOKEN`; Actions injects it automatically per run.
290
+
291
+ ### 2. Allow Actions to open PRs
292
+
293
+ Repo **Settings → Actions → General → Workflow permissions**:
294
+
295
+ - Select **Read and write permissions**.
296
+ - Check **Allow GitHub Actions to create and approve pull requests**.
297
+
298
+ Without the second box, the issue→PR bot can push a branch but can't open the PR.
299
+
300
+ ### 2b. Who can trigger the bots (author-association gating)
301
+
302
+ The write-capable workflows (`mitts-issue`, `mitts-pr-followup`) and the
303
+ on-demand `@mitts review` comment path only run when the triggering commenter's
304
+ `author_association` is `OWNER`, `MEMBER`, or `COLLABORATOR`. On a **public repo**
305
+ this matters: without it, any stranger could comment `@mitts implement …` and
306
+ drive an agent that runs with `contents`/`pull-requests` write and burns your LLM
307
+ budget. If you want a non-collaborator to be able to trigger the bot, add them as a
308
+ repo collaborator (or org member) rather than loosening the gate.
309
+
310
+ The automatic PR review on `opened`/`reopened` stays open by design — it's
311
+ read-only (`contents: read`, only posts a review), which is the whole point of
312
+ reviewing incoming contributions.
313
+
314
+ ### 3. Commit the workflows
315
+
316
+ ```bash
317
+ git checkout -b add-mitts-bots
318
+ git add AGENTS.md .agents/ .github/mitts .github/actions/run-mitts .github/workflows/mitts-*.yml docs/mitts/
319
+ git commit -m "Add Mitts GitHub Actions bots (review, issue, PR follow-up)"
320
+ git push -u origin add-mitts-bots
321
+ gh pr create --fill
322
+ ```
323
+
324
+ > ⚠️ `issue_comment` triggers only fire once the workflow file exists **on the
325
+ > default branch (`main`)**. The bots go live only after this PR is merged. This
326
+ > is a GitHub rule, not a config choice.
327
+
328
+ ### 4. First run
329
+
330
+ Trigger one (e.g. comment `@mitts review` on a PR) and watch it:
331
+
332
+ ```bash
333
+ gh run watch
334
+ ```
335
+
336
+ Most likely first-run issue is the model string / base URL. If the LLM call
337
+ errors, adjust `OPENHANDS_LLM_MODEL` (with/without `openai/`) or the `/v1` on the
338
+ base URL, then re-trigger.
339
+
340
+ ---
341
+
342
+ ## Usage
343
+
344
+ | You want to… | Where | Comment |
345
+ |---|---|---|
346
+ | Review a PR | PR | automatic on open/reopen, or `@mitts review` |
347
+ | Turn an issue into a PR | issue | `@mitts implement <optional detail>` |
348
+ | Ask about an issue | issue | `@mitts what would this involve?` (answers, no PR) |
349
+ | Change code on a PR | PR | `@mitts add error handling for nil config` |
350
+ | Ask about a PR | PR | `@mitts why did you use a mutex here?` (uses cross-run memory) |
351
+ | Propose a plan (no code) | issue or PR | `@mitts plan <optional detail>` |
352
+
353
+ ---
354
+
355
+ ## Environment knobs
356
+
357
+ | Variable | Default | Description |
358
+ |---|---|---|
359
+ | `OPENHANDS_LOAD_PUBLIC_SKILLS` | off | Set to `1` or `true` to also load skills from the `OpenHands/extensions` public registry. Adds ~1–2 s (git clone on first run). |
360
+ | `EXTENSIONS_REF` | `main` | Branch/tag/SHA for the public extensions repo. Pin to a tag for reproducibility once you're happy. |
361
+ | `LLM_MAX_ITER` | `60` | Max agent iterations per run. |
362
+ | `OPENHANDS_PERSIST_DIR` | — | Root directory for cross-run event persistence (set by the composite action via `runner.temp`). |
363
+ | `OPENHANDS_CONVERSATION_KEY` | — | Thread key for the artifact (e.g. `pr-42`, `issue-7`). Set by the composite action from the surface + number. |
364
+ | `OPENHANDS_KEEP_RUNS` | `5` | How many prior run subdirs to retain per thread before a run; older ones are pruned so the artifact stays bounded. |
365
+
366
+ ---
367
+
368
+ ## Things to know
369
+
370
+ - **Only maintainers can trigger the write bots.** The issue, PR-follow-up, and
371
+ on-demand-review comment paths gate on `author_association` ∈
372
+ {`OWNER`,`MEMBER`,`COLLABORATOR`} (see Setup §2b). Non-collaborator comments are
373
+ ignored. The auto PR review on open/reopen is intentionally ungated (read-only).
374
+ - **Write, not admin (structural containment).** Protect `main` and gate the
375
+ RubyGems publish secret behind a `main`-only Environment so a prompt-injected
376
+ agent still cannot merge code or read the release token. The `block_dangerous.sh`
377
+ hook is defense-in-depth on top of this, not a replacement — see "Structural
378
+ containment" above.
379
+ - **Fork PRs get no secrets** (GitHub security rule). Review is skipped and
380
+ follow-up posts a "can't run on forks" note (and runs stateless, no artifacts).
381
+ This is expected. Your own branches work fully.
382
+ - **Cross-run memory horizon**: the recall tool only sees what's still in the
383
+ artifact. Artifacts have `retention-days: 30` and are overwritten per thread, and
384
+ each run keeps only the last `OPENHANDS_KEEP_RUNS` runs (default 5), so the bot
385
+ "remembers" recent prior reasoning on a PR/issue within that window.
386
+ - **Concurrency**: runs are serialized per surface so they don't race on the shared
387
+ state artifact — `mitts-issue-<n>` for the issue bot and `mitts-pr-<n>` for
388
+ the PR follow-up bot (both queue; nothing is dropped). PR review is read-only (no
389
+ artifact, no branch push) and stays on its own `mitts-review-<n>` group where a
390
+ newer review supersedes an in-flight one.
391
+ - **Ruby project**: the write-capable workflows set up Ruby so the agent can run
392
+ the suite with `ruby test/run_all_tests.rb` before committing.
393
+ - **Cost / runtime**: `agent_task.py` caps iterations via `LLM_MAX_ITER`
394
+ (default 60). Cost is reported in the Actions step summary after each run. The
395
+ `pr-review` action also accepts budget limits. There is no hard dollar cap by
396
+ default — add one if your endpoint bills per token.
397
+ - **No workflow chaining by default**: the built-in `GITHUB_TOKEN` does not
398
+ retrigger other workflows, so a PR the bot opens won't auto-run `test.yml`.
399
+ Use a PAT or GitHub App token if you want that. (Consequence for CI-waiting: on a
400
+ bot-opened PR the checks tab may be empty — that's expected, not a hang. The
401
+ `ci-monitoring` skill tells the agent to note this rather than poll forever.)
402
+ - **Pinning**: workflows reference `OpenHands/extensions@main`. Pin to a tag or
403
+ SHA (`extensions-version:`) once you're happy, for reproducibility.
404
+ - **Adding skills**: create a new directory under `.agents/skills/<skill-name>/`
405
+ with a `SKILL.md` following the AgentSkills format. It will be picked up
406
+ automatically on the next run. See `.agents/skills/run-tests/SKILL.md` for an
407
+ example.
408
+
409
+ ---
410
+
411
+ ## References
412
+
413
+ - OpenHands: <https://github.com/OpenHands/OpenHands>
414
+ - Extensions registry (plugins): <https://github.com/OpenHands/extensions>
415
+ - Agent SDK: <https://github.com/OpenHands/software-agent-sdk>
416
+ - Docs: <https://docs.openhands.dev>
417
+ - AgentSkills standard: <https://agentskills.io/specification>