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.
- checksums.yaml +4 -4
- data/.agents/skills/ci-monitoring/SKILL.md +103 -0
- data/.agents/skills/run-tests/SKILL.md +45 -0
- data/.claude/auto-fix.md +7 -0
- data/.claude/settings.local.json +21 -0
- data/AGENTS.md +46 -0
- data/CHANGELOG.md +93 -0
- data/README.md +159 -21
- data/docs/1.1.0_UPGRADE_GUIDE.md +269 -0
- data/docs/API/COMMON.md +7 -0
- data/docs/API/CONFIG_CHANGE.md +39 -0
- data/docs/API/CWD_CHANGED.md +34 -0
- data/docs/API/ELICITATION.md +44 -0
- data/docs/API/ELICITATION_RESULT.md +43 -0
- data/docs/API/FILE_CHANGED.md +37 -0
- data/docs/API/INSTRUCTIONS_LOADED.md +30 -0
- data/docs/API/MESSAGE_DISPLAY.md +38 -0
- data/docs/API/NOTIFICATION.md +1 -0
- data/docs/API/PERMISSION_DENIED.md +37 -0
- data/docs/API/PERMISSION_REQUEST.md +199 -0
- data/docs/API/POST_COMPACT.md +30 -0
- data/docs/API/POST_TOOL_BATCH.md +39 -0
- data/docs/API/POST_TOOL_USE.md +1 -0
- data/docs/API/POST_TOOL_USE_FAILURE.md +31 -0
- data/docs/API/PRE_COMPACT.md +10 -2
- data/docs/API/PRE_TOOL_USE.md +9 -1
- data/docs/API/SETUP.md +37 -0
- data/docs/API/STOP_FAILURE.md +19 -0
- data/docs/API/SUBAGENT_START.md +40 -0
- data/docs/API/TASK_COMPLETED.md +43 -0
- data/docs/API/TASK_CREATED.md +43 -0
- data/docs/API/TEAMMATE_IDLE.md +40 -0
- data/docs/API/USER_PROMPT_EXPANSION.md +43 -0
- data/docs/API/WORKTREE_CREATE.md +39 -0
- data/docs/API/WORKTREE_REMOVE.md +33 -0
- data/docs/PROMPT_BASED_HOOKS.md +386 -0
- data/docs/external/claude-hooks-reference.md +2846 -18
- data/docs/mitts/ideabox.md +12 -0
- data/docs/mitts/prd.md +417 -0
- data/docs/mitts/setup.md +303 -0
- data/docs/mitts/task.md +44 -0
- data/lib/claude_hooks/base.rb +30 -1
- data/lib/claude_hooks/config_change.rb +28 -0
- data/lib/claude_hooks/cwd_changed.rb +28 -0
- data/lib/claude_hooks/elicitation.rb +61 -0
- data/lib/claude_hooks/elicitation_result.rb +57 -0
- data/lib/claude_hooks/file_changed.rb +41 -0
- data/lib/claude_hooks/instructions_loaded.rb +23 -0
- data/lib/claude_hooks/message_display.rb +58 -0
- data/lib/claude_hooks/notification.rb +5 -1
- data/lib/claude_hooks/output/base.rb +48 -1
- data/lib/claude_hooks/output/config_change.rb +47 -0
- data/lib/claude_hooks/output/cwd_changed.rb +42 -0
- data/lib/claude_hooks/output/elicitation.rb +67 -0
- data/lib/claude_hooks/output/elicitation_result.rb +63 -0
- data/lib/claude_hooks/output/file_changed.rb +42 -0
- data/lib/claude_hooks/output/instructions_loaded.rb +19 -0
- data/lib/claude_hooks/output/message_display.rb +49 -0
- data/lib/claude_hooks/output/permission_denied.rb +42 -0
- data/lib/claude_hooks/output/permission_request.rb +120 -0
- data/lib/claude_hooks/output/post_compact.rb +18 -0
- data/lib/claude_hooks/output/post_tool_batch.rb +51 -0
- data/lib/claude_hooks/output/post_tool_use.rb +12 -0
- data/lib/claude_hooks/output/post_tool_use_failure.rb +42 -0
- data/lib/claude_hooks/output/pre_compact.rb +34 -3
- data/lib/claude_hooks/output/pre_tool_use.rb +26 -2
- data/lib/claude_hooks/output/session_start.rb +30 -8
- data/lib/claude_hooks/output/setup.rb +42 -0
- data/lib/claude_hooks/output/stop.rb +15 -0
- data/lib/claude_hooks/output/stop_failure.rb +19 -0
- data/lib/claude_hooks/output/subagent_start.rb +42 -0
- data/lib/claude_hooks/output/task_completed.rb +18 -0
- data/lib/claude_hooks/output/task_created.rb +18 -0
- data/lib/claude_hooks/output/teammate_idle.rb +18 -0
- data/lib/claude_hooks/output/user_prompt_expansion.rb +51 -0
- data/lib/claude_hooks/output/worktree_create.rb +60 -0
- data/lib/claude_hooks/output/worktree_remove.rb +19 -0
- data/lib/claude_hooks/permission_denied.rb +41 -0
- data/lib/claude_hooks/permission_request.rb +63 -0
- data/lib/claude_hooks/post_compact.rb +23 -0
- data/lib/claude_hooks/post_tool_batch.rb +59 -0
- data/lib/claude_hooks/post_tool_use.rb +15 -1
- data/lib/claude_hooks/post_tool_use_failure.rb +45 -0
- data/lib/claude_hooks/pre_compact.rb +7 -0
- data/lib/claude_hooks/pre_tool_use.rb +34 -1
- data/lib/claude_hooks/session_start.rb +28 -0
- data/lib/claude_hooks/setup.rb +25 -0
- data/lib/claude_hooks/stop.rb +17 -0
- data/lib/claude_hooks/stop_failure.rb +27 -0
- data/lib/claude_hooks/subagent_start.rb +23 -0
- data/lib/claude_hooks/subagent_stop.rb +5 -1
- data/lib/claude_hooks/task_completed.rb +35 -0
- data/lib/claude_hooks/task_created.rb +35 -0
- data/lib/claude_hooks/teammate_idle.rb +23 -0
- data/lib/claude_hooks/user_prompt_expansion.rb +45 -0
- data/lib/claude_hooks/version.rb +1 -1
- data/lib/claude_hooks/worktree_create.rb +27 -0
- data/lib/claude_hooks/worktree_remove.rb +19 -0
- data/lib/claude_hooks.rb +42 -0
- 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>
|