hivecode-mcp 0.12.4 → 0.13.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.
@@ -0,0 +1,310 @@
1
+ # Always on: agents that work while your laptop is closed
2
+
3
+ The GitHub watcher (`npx -y hivecode-mcp@latest github start`) does its work on the machine it runs
4
+ on. When that is a laptop and the lid closes, everything pauses: no issue is picked up, and a run in
5
+ progress stops. On wake, the watcher pushes each deadline back by the time it was asleep, so nothing
6
+ is killed for time it never had. But nothing happens overnight either.
7
+
8
+ For "your agents keep working after you go home", run the watcher on a machine that stays on.
9
+
10
+ ## What the machine needs
11
+
12
+ - Node.js 18 or newer, git, and the [GitHub CLI](https://cli.github.com).
13
+ - An agent CLI that runs headless, such as [Claude Code](https://docs.claude.com/en/docs/claude-code).
14
+ - A clone of your repository.
15
+ - `npx -y hivecode-mcp@latest setup`, run once, so the agent has its Hivecode tools.
16
+
17
+ Logins on a machine with no browser:
18
+ - **GitHub:** `gh auth login --with-token < token.txt`, using a fine-grained token with access to
19
+ this repository only. It needs read and write on contents, issues and pull requests, and read on
20
+ actions and checks.
21
+ - **The agent:** follow its own instructions for a server. Claude Code takes an API key
22
+ (`ANTHROPIC_API_KEY`) from the environment.
23
+ - **Slack (optional):** if `--slack-channel` was saved on your test start, `SLACK_BOT_TOKEN` must be
24
+ available in the environment (and `SLACK_APP_TOKEN` if using `--slack-allow`).
25
+
26
+ ---
27
+
28
+ ## The exit-code contract
29
+
30
+ The watcher (`github start`) reports its exit reason via standardized process exit codes:
31
+
32
+ | Exit code | Meaning | Service action |
33
+ |---|---|---|
34
+ | `0` | Clean stop (Ctrl+C, SIGTERM, normal exit) | Do not restart. |
35
+ | `1` | Transient failure (boot-time network glitch, `Q603 could not reach GitHub`, transient API drop, unexpected crash) | **Restart.** |
36
+ | `2` | Permanent refusal (`Q600` gh not logged in, `Q601` push denied, `Q602` agent not on PATH, `Q605` lock held, `Q633` missing bot token, invalid CLI flags) | **Do not restart.** |
37
+
38
+ > **Note:** The 0/1/2 exit-code contract was introduced in `hivecode-mcp@0.13.0`. Older versions (such as `0.12.4`) exited `1` for all errors, causing systemd's `RestartPreventExitStatus=2` to be bypassed and falling back on `StartLimit` rate-limiting alone.
39
+
40
+ Configuring your service to distinguish exit `1` from exit `2` prevents infinite restart loops when a lock is held or credentials are missing.
41
+
42
+ ---
43
+
44
+ ## Linux (systemd service)
45
+
46
+ First, run the watcher once by hand with your options in the clone to verify it and save settings:
47
+
48
+ ```bash
49
+ npx -y hivecode-mcp@latest github start --agent "claude -p --output-format json --allowedTools Bash Edit Write Read Glob Grep mcp__hivecode" --test "npm test"
50
+ ```
51
+
52
+ Stop it with Ctrl+C. The options are now saved in the clone's `.git/hivecode/github.json` file.
53
+
54
+ ### 1. Store secrets in an EnvironmentFile (never in the unit file)
55
+
56
+ Never put API tokens directly into a `.service` file: unit files are typically readable by any user on
57
+ the system and leak into `systemctl show`.
58
+
59
+ Create an environment file readable only by the service user:
60
+
61
+ ```bash
62
+ sudo mkdir -p /etc/hivecode
63
+ sudo touch /etc/hivecode/watcher.env
64
+ sudo chown hive:hive /etc/hivecode/watcher.env
65
+ sudo chmod 600 /etc/hivecode/watcher.env
66
+ ```
67
+
68
+ Add your credentials to `/etc/hivecode/watcher.env`:
69
+
70
+ ```ini
71
+ ANTHROPIC_API_KEY=sk-ant-api03-...
72
+ # Optional: if using Slack threads
73
+ SLACK_BOT_TOKEN=xoxb-...
74
+ SLACK_APP_TOKEN=xapp-...
75
+ ```
76
+
77
+ ### 2. Discover binary paths
78
+
79
+ systemd runs with a minimal `PATH` that does not include user-installed tools (`nvm`, `fnm`, or
80
+ `~/.local/bin`). Run this as the service user to find the exact locations of `npx`, `claude`, and `gh`:
81
+
82
+ ```bash
83
+ sudo -u hive -i command -v npx claude gh
84
+ ```
85
+
86
+ Copy the directory paths containing these binaries into the `Environment="PATH=..."` line below.
87
+
88
+ ### 3. Create the systemd unit
89
+
90
+ Write `/etc/systemd/system/hivecode-watcher.service`. Pin the version of `hivecode-mcp` you tested:
91
+
92
+ ```ini
93
+ [Unit]
94
+ Description=Hivecode GitHub watcher
95
+ After=network-online.target
96
+ Wants=network-online.target
97
+ StartLimitIntervalSec=300
98
+ StartLimitBurst=5
99
+
100
+ [Service]
101
+ User=hive
102
+ WorkingDirectory=/home/hive/your-repo
103
+ EnvironmentFile=/etc/hivecode/watcher.env
104
+ Environment="PATH=/home/hive/.nvm/versions/node/v20.18.0/bin:/home/hive/.local/bin:/usr/local/bin:/usr/bin:/bin"
105
+ ExecStart=/home/hive/.nvm/versions/node/v20.18.0/bin/npx -y hivecode-mcp@<the version you tested> github start
106
+ Restart=on-failure
107
+ RestartSec=30
108
+ RestartPreventExitStatus=2
109
+
110
+ [Install]
111
+ WantedBy=multi-user.target
112
+ ```
113
+
114
+ Key settings:
115
+ - `Restart=on-failure` with `RestartPreventExitStatus=2`: restarts if the network drops (exit 1), but halts immediately on permanent refusals like missing tokens or lock collisions (exit 2).
116
+ - `StartLimitIntervalSec=300` and `StartLimitBurst=5`: rate-limits unexpected crash loops to 5 attempts per 5 minutes.
117
+
118
+ Enable and inspect:
119
+
120
+ ```bash
121
+ sudo systemctl enable --now hivecode-watcher
122
+ journalctl -u hivecode-watcher -f
123
+ ```
124
+
125
+ ---
126
+
127
+ ## macOS (launchd LaunchAgent)
128
+
129
+ Run the watcher as a user LaunchAgent so it has access to user keychain credentials, git logins, and developer tools.
130
+
131
+ ### 1. Store secrets in an environment file (never in the plist)
132
+
133
+ `launchd` runs as a system process outside your interactive shell, so it does **not** source `~/.zshrc` or `~/.bash_profile`. Putting API keys directly into the `.plist` file leaves them in plaintext in `~/Library/LaunchAgents`.
134
+
135
+ Instead, create an environment file owned by you with mode `0600`:
136
+
137
+ ```bash
138
+ mkdir -p ~/.config/hivecode
139
+ touch ~/.config/hivecode/watcher.env
140
+ chmod 600 ~/.config/hivecode/watcher.env
141
+ ```
142
+
143
+ Add your keys to `~/.config/hivecode/watcher.env`:
144
+
145
+ ```ini
146
+ ANTHROPIC_API_KEY=sk-ant-api03-...
147
+ SLACK_BOT_TOKEN=xoxb-...
148
+ ```
149
+
150
+ *(Alternatively, macOS Keychain can supply secrets dynamically: `security find-generic-password -w -s "anthropic-api-key"`.)*
151
+
152
+ ### 2. Create the wrapper script
153
+
154
+ Create `~/.config/hivecode/run-watcher.sh` (`chmod 700 ~/.config/hivecode/run-watcher.sh`):
155
+
156
+ ```bash
157
+ #!/bin/bash
158
+ set -a
159
+ [ -f "$HOME/.config/hivecode/watcher.env" ] && . "$HOME/.config/hivecode/watcher.env"
160
+ set +a
161
+ exec /usr/local/bin/npx -y hivecode-mcp@<the version you tested> github start
162
+ ```
163
+
164
+ ### 3. Create the LaunchAgent plist
165
+
166
+ Create `~/Library/LaunchAgents/com.hivecode.watcher.plist`:
167
+
168
+ ```xml
169
+ <?xml version="1.0" encoding="UTF-8"?>
170
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
171
+ <plist version="1.0">
172
+ <dict>
173
+ <key>Label</key>
174
+ <string>com.hivecode.watcher</string>
175
+ <key>WorkingDirectory</key>
176
+ <string>/Users/youruser/your-repo</string>
177
+ <key>ProgramArguments</key>
178
+ <array>
179
+ <string>/bin/bash</string>
180
+ <string>/Users/youruser/.config/hivecode/run-watcher.sh</string>
181
+ </array>
182
+ <key>EnvironmentVariables</key>
183
+ <dict>
184
+ <key>PATH</key>
185
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
186
+ </dict>
187
+ <key>KeepAlive</key>
188
+ <dict>
189
+ <key>SuccessfulExit</key>
190
+ <false/>
191
+ </dict>
192
+ <key>ThrottleInterval</key>
193
+ <integer>60</integer>
194
+ <key>StandardOutPath</key>
195
+ <string>/Users/youruser/Library/Logs/hivecode-watcher.log</string>
196
+ <key>StandardErrorPath</key>
197
+ <string>/Users/youruser/Library/Logs/hivecode-watcher.err</string>
198
+ </dict>
199
+ </plist>
200
+ ```
201
+
202
+ > **Note on launchd restart policy:** Unlike systemd, `launchd` does not have a filter like `RestartPreventExitStatus`; `KeepAlive` with `SuccessfulExit: false` restarts on any non-zero exit code. Set `ThrottleInterval` to at least `60` seconds so a permanent error does not burn CPU, and check `~/Library/Logs/hivecode-watcher.err` if the service stops making progress.
203
+
204
+ Load and monitor:
205
+
206
+ ```bash
207
+ launchctl load ~/Library/LaunchAgents/com.hivecode.watcher.plist
208
+ tail -f ~/Library/Logs/hivecode-watcher.err
209
+ ```
210
+
211
+ To stop: `launchctl unload ~/Library/LaunchAgents/com.hivecode.watcher.plist`.
212
+
213
+ ---
214
+
215
+ ## Windows (Task Scheduler at Logon)
216
+
217
+ Run the watcher as a scheduled task at user logon. Do not run it as an isolated Windows Service (Session 0), which lacks user credential managers and interactive desktop configurations.
218
+
219
+ ### 1. Set user environment variables (never put keys in scripts)
220
+
221
+ Do not put API tokens inside `.cmd` or `.ps1` files. Set them as per-user environment variables in Windows Settings (System > About > Advanced system settings > Environment Variables), or via PowerShell:
222
+
223
+ ```powershell
224
+ [Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", "sk-ant-api03-...", "User")
225
+ ```
226
+
227
+ The logon task inherits these variables automatically. The wrapper script contains no secrets.
228
+
229
+ ### 2. Create the runner wrapper
230
+
231
+ Because Task Scheduler's native retry triggers do not inspect process exit codes, create a wrapper script that enforces the rule: **halt only on clean stop (0) and permanent refusal (2); retry on everything else**.
232
+
233
+ Save as `%USERPROFILE%\.hivecode\run-watcher.cmd`:
234
+
235
+ ```bat
236
+ @echo off
237
+ setlocal
238
+ cd /d "C:\path\to\your-repo"
239
+
240
+ :loop
241
+ call npx -y hivecode-mcp@<the version you tested> github start >> "%USERPROFILE%\.hivecode\watcher.log" 2>&1
242
+ set "CODE=%ERRORLEVEL%"
243
+
244
+ if %CODE% EQU 0 (
245
+ echo [%DATE% %TIME%] Watcher stopped cleanly (exit 0). >> "%USERPROFILE%\.hivecode\watcher.log"
246
+ exit /b 0
247
+ )
248
+
249
+ if %CODE% EQU 2 (
250
+ echo [%DATE% %TIME%] Watcher permanent refusal (exit 2). Halting service. Check logs above. >> "%USERPROFILE%\.hivecode\watcher.log"
251
+ exit /b 2
252
+ )
253
+
254
+ echo [%DATE% %TIME%] Watcher exited with code %CODE%. Retrying in 30s... >> "%USERPROFILE%\.hivecode\watcher.log"
255
+ timeout /t 30 /nobreak >nul
256
+ goto loop
257
+ ```
258
+
259
+ ### 3. Register the task (requires Administrator elevation)
260
+
261
+ > **Elevation required:** Open Command Prompt or PowerShell with **Run as administrator**. Registering a task via `schtasks` modifies `%SystemRoot%\System32\Tasks`; running from a standard (unelevated) prompt fails with `ERROR: Access is denied.`
262
+
263
+ ```cmd
264
+ schtasks /Create /TN "HivecodeWatcher" /TR "cmd /c %USERPROFILE%\.hivecode\run-watcher.cmd" /SC ONLOGON /RL LIMITED
265
+ ```
266
+
267
+ Inspect output with:
268
+
269
+ ```cmd
270
+ type %USERPROFILE%\.hivecode\watcher.log
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Power and sleep settings
276
+
277
+ The watcher pauses when the operating system enters sleep. Ensure sleep is disabled when plugged in:
278
+
279
+ - **Linux:** Server installations typically do not suspend. On desktop Linux, mask sleep targets if needed:
280
+ ```bash
281
+ sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
282
+ ```
283
+ - **macOS:** In **System Settings > Energy Saver** (or **Displays**), enable **Prevent automatic sleeping when the display is off**. Alternatively, run `caffeinate -s`.
284
+ - **Windows:** In **Settings > System > Power & battery**, set **When plugged in, put my device to sleep after** to **Never**.
285
+
286
+ ---
287
+
288
+ ## Status check
289
+
290
+ Regardless of operating system, you can inspect watcher state from inside the repository clone at any time:
291
+
292
+ ```bash
293
+ npx -y hivecode-mcp@latest github status
294
+ ```
295
+
296
+ ---
297
+
298
+ ## What can go wrong
299
+
300
+ | what goes wrong | what happens |
301
+ |---|---|
302
+ | Transient boot network failure (exit 1) | The service waits 30 seconds and retries automatically. |
303
+ | Two copies start (exit 2) | The second copy refuses immediately (`Q605`: one watcher per clone) and does not restart. |
304
+ | Missing API keys or tokens (exit 2) | The watcher halts with `Q600` or `Q633` and logs the remediation fix. |
305
+ | Agent not on PATH (exit 2) | The watcher halts with `Q602` without looping. Check binary directories with `command -v`. |
306
+ | `@latest` changes under a running service | Pin a version in the service file (e.g. `hivecode-mcp@<the version you tested>`), as above, and upgrade on purpose. |
307
+ | The GitHub token expires | The watcher stops asking and logs `Q600` in the service log. |
308
+ | The agent's credits run out | Each issue fails once, waits 15 minutes, fails again, and transitions to `hivecode:needs-human` with the agent's explanation. |
309
+ | The disk fills | Finished issue rooms are removed automatically; only the last failed run's directory is preserved for inspection. |
310
+ | Service user privileges | An agent can execute commands as the service user. Use a dedicated user (`hive`) with access only to the project repository. |
@@ -0,0 +1,334 @@
1
+ # GitHub workflow: label an issue, get back a tested PR
2
+
3
+ Status, 13 Oct 2026:
4
+ - **Built:** the watcher (`hive-github.js`).
5
+ - **Tested:** `tests/github-watcher-test.mjs` runs a real relay, real git with a real `origin`, and
6
+ the real MCP join, against a fake GitHub and a fake agent.
7
+ - **Mutants:** `scripts/github-mutants.mjs` removes each defence in turn and checks that its row
8
+ goes red.
9
+ - **Not yet run:** against a real repository with a real agent.
10
+
11
+ The tables below describe what the code does.
12
+
13
+ ## What the user does
14
+
15
+ 1. Once per repo, in their clone:
16
+ `npx -y hivecode-mcp@latest github start --agent "<agent command>"`
17
+ It checks that `gh` is logged in, that the agent command exists and can use Hivecode, and that the
18
+ labels exist, then keeps running and watches the repo.
19
+ **Private code:** every run puts the branch in a room on a relay, by default our public one.
20
+ Run your own (`npx -y hivecode-relay`, see docs/SELF_HOSTING.md) and add
21
+ `--relay wss://<your relay>`.
22
+ 2. They label an issue `hivecode`.
23
+ 3. A PR comes back, with:
24
+ - the issue's title;
25
+ - `Closes #12`;
26
+ - what changed and why;
27
+ - which tests ran and what they returned;
28
+ - one commit per agent.
29
+ 4. If CI fails or they review the PR, the agent gets it and pushes fixes to the same PR.
30
+ - Inline comments and reviews always go to the agent.
31
+ - A plain comment goes only if it starts with `/hivecode`, so "thanks" or "LGTM" never spends a run.
32
+ 5. They merge. To stop at any point, they remove the label or comment `/hivecode stop`.
33
+ `/hivecode retry` starts it again.
34
+
35
+ They never open a terminal for an issue and never watch a chat.
36
+
37
+ ### See it first: `--dry-run`
38
+
39
+ `github start --dry-run` reads GitHub once. For each labelled issue it says what a start would do
40
+ and what is in the way. It writes nothing anywhere: no label, comment, branch, lock, watcher id or
41
+ state file, and it starts no agent. Use it first on a repo that matters. It is also the first
42
+ answer to "why isn't it picking up my issue?".
43
+
44
+ `--repo <folder>` points `start` or `status` at that clone, from any folder.
45
+
46
+ | what goes wrong | what the dry run does |
47
+ |---|---|
48
+ | A watcher has run on this clone before | It reads the state file as it is, and never moves or rewrites it. An issue that is in review, stopped, closed, or waiting for a person is reported as that, with what moves it on. It is never "would start now". |
49
+ | Today's runs are used up | It says "would wait: today's limit of 20 runs is reached". |
50
+ | Another watcher claimed the issue | It says "would skip". It reads the claim comment, as a start would. |
51
+ | This clone lost its state file, and the issue has our claim and an open PR | It says it would pick that PR up again, without a run. A start does exactly that. |
52
+ | A watcher is running on this clone right now | It says so first. A dry run takes no lock, so it neither stops nor blocks that watcher. |
53
+ | The state file says a run is going, and no watcher is alive | It says the run was cut off, and that a start runs it again. |
54
+ | The state file cannot be read | It leaves the file where it is, and says that a start would move it aside and rebuild from GitHub. `status` leaves it alone too. |
55
+ | GitHub fails to list the issues | It says so and exits 1. It never reports "no labelled issues". |
56
+
57
+ ### Progress in Slack: `--slack-channel`
58
+
59
+ `github start --slack-channel C0123456789`, with the Slack app's bot token in `SLACK_BOT_TOKEN`,
60
+ gives each issue **one Slack thread**. The thread opens with the issue's number, title and link.
61
+ Inside it:
62
+ - each run starting;
63
+ - the pull request, which is also shown in the channel;
64
+ - CI failures;
65
+ - the tester's verdict;
66
+ - waiting for a person, which is also shown in the channel;
67
+ - merged or closed, also shown in the channel.
68
+
69
+ Setting up the Slack app is in [SLACK_SETUP.md](SLACK_SETUP.md).
70
+
71
+ | what goes wrong | what we do |
72
+ |---|---|
73
+ | No `SLACK_BOT_TOKEN` | `start` refuses with Q633 and the fix, before anything is written. |
74
+ | A token Slack refuses, or a bot that is not in the channel | Checked against Slack before any work starts (Q630, Q631), naming the `/invite` that fixes it. |
75
+ | Slack says "ready for review" for a PR that GitHub refused | Every post goes out only after the GitHub write it reports has succeeded. |
76
+ | A restart opens a second thread for the same issue | The thread id is kept in the watcher's state file, so a restart continues the same thread. |
77
+ | The room's id reaches Slack | It is never posted. Anyone with the id can read the code in the room, and Slack is outside it. |
78
+ | Slack is down, or the token is revoked mid-run | The work on GitHub goes on. The refusal is said once per reason, not once per post. |
79
+ | The token ends up somewhere it shouldn't | It is read from the environment only. It is never a flag, so it is never saved with the flags or left in shell history. |
80
+ | A secret in an agent's summary | Scrubbed before posting, the same as on GitHub. |
81
+
82
+ **Replies from Slack.** With `SLACK_APP_TOKEN` set as well (Socket Mode, so no public URL is
83
+ needed), a reply of `stop` in an issue's thread stops that issue. A reply of `retry` starts it
84
+ again, but only from the Slack user ids in `--slack-allow`. The watcher turns the reply into the
85
+ same `/hivecode stop` or `/hivecode retry` comment on the issue, "(from Slack, by @handle)". So one
86
+ command path acts on it, and GitHub keeps who asked.
87
+
88
+ | what goes wrong | what we do |
89
+ |---|---|
90
+ | A guest, or someone from another workspace (Slack Connect), replies `stop` | Refused, and told so in the thread. Who may is decided by the same rules as `slack start`, in one place in `hive-slack.js`. |
91
+ | A full member who is not in `--slack-allow` replies `retry` | Refused, and told so. Stopping is the safe direction, so any full member may stop, but only named people may start agents again. |
92
+ | Slack cannot confirm who replied `stop` (rate limit, outage) | It stops anyway, and says it could not confirm who asked. `retry` stays refused. |
93
+ | GitHub refuses the comment | Slack is told "Could not stop: GitHub refused". It never says "stopping" for a stop that did not happen. |
94
+ | A reply in a thread the watcher does not own | Ignored. |
95
+ | `--slack-allow` without `SLACK_APP_TOKEN` | Start refuses (Q634) and says to set it. Without it the replies are never read, so nobody could retry from Slack, while the start looked fine. |
96
+ | `--slack-allow` without `--slack-channel` | Start refuses (Q634): there are no threads to reply in. |
97
+ | `SLACK_APP_TOKEN` set, but the bot token lacks `users:read` or the channel's history scope (`channels:history`, or `groups:history` for a private channel) | Start refuses (Q634), names the missing scopes, and says to reinstall the app from `slack-app-manifest-inbound.json`. Without the history scope Slack never sends the replies, so a `stop` would silently do nothing; without `users:read` every stop is "unverified" and every retry refused. Slack reports a token's scopes on every answer; if it does not, start says so and goes on. |
98
+ | `--slack-allow ganesh` or `@ganesh` (a name, not a member id) | Start refuses (Q634) and says where the id is: profile, More, Copy member ID. Slack sends ids like `U012AB3CD`, so a name would never match and every retry would be refused. |
99
+ | A `--slack-allow` (or `--slack-channel`) saved by the last start, refused now | The error says it was saved by the last start, and that passing it empty (`--slack-allow ""`) drops it. Every flag is saved and used again by the next start. |
100
+
101
+ ## How it works
102
+
103
+ The watcher is one process on a machine that has the clone, `gh` logged in, and an agent CLI:
104
+ Claude Code, Codex or Gemini CLI. That machine can be a laptop, a desktop or a VM.
105
+
106
+ Every minute it asks GitHub, through `gh`, what changed. Nothing has to reach in from outside, so it
107
+ works behind a company firewall, and no server of ours ever holds a GitHub key.
108
+
109
+ For each labelled issue:
110
+
111
+ | step | what happens |
112
+ |---|---|
113
+ | claim | It comments on the issue, with a hidden marker so no second watcher takes it, and creates the branch `hivecode/12-<slug>` from the default branch. |
114
+ | run | It opens a fresh room seeded from that branch and starts the agent with the task on stdin. It waits, with a time limit. |
115
+ | land | It makes one commit per agent on the branch and pushes. The first run opens the PR. Later runs push to that PR and comment on what they changed. |
116
+ | test | Optional `--test "<command>"`. The watcher runs the project's tests itself before pushing, so "tested" is a fact we checked, not the agent's word. |
117
+ | watch | It reads the CI result for the PR's newest commit, and the reviews and comments from people with write access. |
118
+ | loop | Each new CI failure, or batch of review comments, becomes the next run's task. The agent gets the failing job's log, not just "failed". A review becomes one run once nothing new has arrived for 90 s, not one run per comment. |
119
+ | finish | When the PR is merged or closed, it stops, removes the room folders and sends a notification. |
120
+
121
+ ### Which agents
122
+
123
+ The agent command gets its task on stdin, from its working folder, and must end with the lines the
124
+ task asks for.
125
+
126
+ | agent | command | status |
127
+ |---|---|---|
128
+ | Claude Code | `claude -p --output-format json --allowedTools Bash Edit Write Read Glob Grep mcp__hivecode` | the default this was built for |
129
+ | Codex | `codex exec --skip-git-repo-check -s workspace-write -` | flags checked against its `--help`; not yet run end to end |
130
+ | Gemini CLI | `gemini -p "Do the task above." --approval-mode auto_edit` | flags checked against its `--help`; not yet run end to end |
131
+
132
+ Codex refuses to run outside a git repository unless given `--skip-git-repo-check`. A room's
133
+ folder is deliberately not a checkout, so the flag is required, not optional.
134
+
135
+ ### A second agent checks the first (`--tester`)
136
+
137
+ `--tester "<command>"` adds a tester. It can be a different vendor's agent, so the check is
138
+ independent.
139
+
140
+ 1. The builder runs, and its work lands on the local branch. Nothing is pushed yet.
141
+ 2. The tester runs in a fresh room seeded from that branch, so it holds the change.
142
+ - It gets the issue and the builder's diff. The room's folder is not a git checkout, so it could
143
+ not see the diff otherwise.
144
+ - It may add or change tests only.
145
+ - It ends with `VERDICT: PASS` or `VERDICT: FAIL`, plus a report.
146
+ 3. On PASS, its tests land, and the branch is pushed. The PR shows commits from both agents, and a
147
+ "Checked by Tester" section with the verdict and report.
148
+ 4. On FAIL, nothing is pushed. Its report, and any failing test it wrote, become the builder's next
149
+ task. Then the tester checks again.
150
+
151
+ Every run counts against the caps, so a tester doubles what an issue costs.
152
+
153
+ Every run gets a new room, seeded from the branch as it is right now. So the room always includes
154
+ commits a person pushed to the branch, and a relay that loses its rooms costs at most the run in
155
+ progress. The watcher keeps its own state in the repo's `.git` folder, never on the relay.
156
+
157
+ Notifications are optional: one Slack or Discord webhook URL. They are sent when:
158
+ - a PR is ready;
159
+ - CI failed (the fix is under way);
160
+ - the issue needs you;
161
+ - the PR was merged or closed;
162
+ - CI has hung for two hours (once);
163
+ - the daily run limit is reached (once).
164
+
165
+ Nothing is sent for each comment.
166
+
167
+ ## Why `gh` and not a GitHub App, for now
168
+
169
+ - **With `gh`, nothing new is exposed.**
170
+ - There's no public endpoint to attack, and no private key on our relay.
171
+ - It works with a self-hosted relay inside a company network.
172
+ - It ships today, without waiting for an app to be registered.
173
+ - **Every API call is the user's own.** They can see each one and revoke it with `gh auth logout`.
174
+ - **The App comes next, for three things `gh` can't give:**
175
+ - PRs and comments from `hivecode[bot]` instead of the user. Under branch protection you cannot
176
+ approve your own PR.
177
+ - Instant webhooks instead of a one-minute poll.
178
+ - A listing on the GitHub Marketplace.
179
+
180
+ The watcher's design does not change. Only where events come from, and who signs the PR.
181
+
182
+ ## What stays the same
183
+
184
+ - Git is the record, and the room is where the work happens.
185
+ - CI, required reviewers and branch protection all keep working as before.
186
+ - Agents never merge. A person does.
187
+
188
+ ## What can go wrong, and what we do
189
+
190
+ "Wrong but green" is the failure this project keeps producing: it says it worked, and it didn't.
191
+ Each row below says what the user sees.
192
+
193
+ ### GitHub
194
+
195
+ | what goes wrong | what we do |
196
+ |---|---|
197
+ | `gh` is missing, logged out, or its token can't do something | `start` refuses and prints the exact fix, e.g. `gh auth login`. A logged-out `gh` gives no HTTP answer at all, only "please run: gh auth login", so that text is read as a login problem (Q600), not as a network outage. If the login stops working later, the watcher holds off and logs it every minute. It also sends one notification, since nobody may be watching that terminal. |
198
+ | The repo doesn't exist, or it is private and the account `gh` uses can't see it | GitHub answers 404 for both. `start` says which repo and which account (Q601), and that a private repo needs an account with access. It doesn't say "is gh logged in?", because the login just worked. |
199
+ | The `origin` URL has a token in it (`https://x-access-token:<token>@...`) | A message that quotes the origin drops everything between `//` and `@`, so the token never reaches a log or a screenshot. |
200
+ | Someone runs `github --help` | Usage goes to stdout with exit 0, before git or GitHub is asked anything. |
201
+ | The `--notify` webhook URL leaks (anyone holding it can post as you) | Only its host is ever printed ("posted to hooks.slack.com"). Given with `--notify`, it is saved with the other flags so a restarted service keeps notifying, in a state file only its owner can read (on Mac and Linux). Given as `HIVE_NOTIFY_WEBHOOK`, it is never saved. Something that is not an http(s) address is refused at start (Q609). |
202
+ | A service manager restarts `github start` (systemd, launchd, Task Scheduler) | The exit code says whether a restart can help: 0 stopped cleanly; 1 GitHub or Slack could not be reached, was busy (5xx, rate limit), or it crashed, so try again; 2 a person must change something (a login, a permission, a flag, a token, or a copy already running). What cannot be told apart exits 1, because a machine that booted before its network came up must not be left without a watcher. Recipes that use this are in [ALWAYS_ON.md](ALWAYS_ON.md). |
203
+ | The `hivecode` label doesn't exist | `start` creates `hivecode` and `hivecode:needs-human`. |
204
+ | A read from GitHub fails (network, outage, 5xx) | **A failed read is never treated as "no issues" or "PR closed".** Nothing changes state on a failed read. We back off and retry, and status says so. |
205
+ | Rate limit, primary or secondary | We wait for `Retry-After` or the reset time and do nothing twice. Status shows it. |
206
+ | The label was added by someone who can't push (a stranger, or a bot) | Skipped. Status and the log name who labelled it. Only write, maintain or admin counts. |
207
+ | The "issue" is a pull request (the issues API returns both) | Skipped. |
208
+ | More than 100 labelled issues | We read every page. They queue oldest first; `--max-parallel` (default 1) run at once. Only a claimed issue gets a comment. |
209
+ | Two watchers on one repo (two terminals, two machines) | A lock file covers one machine. Across machines, the claim comment's marker does: whoever commented first owns the issue. |
210
+ | The branch name is already taken | We add `-2`, `-3`, … We never reuse a branch we didn't create. |
211
+ | Someone pushes to the PR branch | The next run's room is seeded from the branch, so it includes their commits. If they push during a run, our push is refused, nothing of theirs is overwritten, and the issue goes to needs-human. |
212
+ | The default branch moves on, and GitHub says the PR can't be merged | While the issue is idle in review, the watcher merges the default branch into the PR's branch in a worktree of its own, never your checkout, and pushes the merge commit. CI runs again on it. On a conflict it aborts, pushes nothing, and the issue goes to needs-human with the files named. One try per head commit. Repos that require linear history won't take a merge commit: there the push is refused, and that also goes to a person. Agents resolving conflicts comes later. |
213
+ | The issue is edited after it was picked up | Runs use the text as it was when claimed. `/hivecode retry` reads it again, so a person can clarify the issue and retry. |
214
+ | The issue or the PR is closed, or the label is removed | We stop. A run in progress is killed, and we don't push. |
215
+ | The PR is closed without merging, with the label left on | Nothing starts again: closing it is a "no". `/hivecode retry` starts a new attempt. |
216
+ | The PR is reopened | We watch it again. |
217
+ | A huge issue body, or images only | The text is cut to 20 KB, and the task tells the agent so. The task also says how many images the issue has, and that the agent cannot see them. |
218
+ | The state file is lost or unreadable | An unreadable file is set aside, never deleted, and the start says where it went (Q606). Each claim comment names its branch, so the watcher picks up its pull requests, and anything waiting for a person, without running anything again. |
219
+ | CI never finishes (a stuck job) | After 2 hours pending we notify once, and keep watching. |
220
+ | The repo has no CI | The PR waits for review. Nothing is assumed passed. |
221
+
222
+ ### The agent
223
+
224
+ | what goes wrong | what we do |
225
+ |---|---|
226
+ | The agent CLI isn't installed | `start` refuses. |
227
+ | The agent is `claude` and has no `hivecode` MCP server (`setup` never ran on this machine), or has one that does not connect | `start` (and `--dry-run`) refuses (Q602) with `npx -y hivecode-mcp@latest setup`, after asking `claude mcp get hivecode`, which is read-only and spends no tokens. Without it the first run fails only after the user has paid for it. `claude` not answering in 60 s is said ("could not check"), never passed. `--skip-agent-check` is for a server set up under another name. |
228
+ | The agent command passes `--allowedTools` without `mcp__hivecode` | `start` refuses (Q602): in `-p` mode the hive tools would be denied, the same paid-for failure. |
229
+ | Any other agent CLI (Codex, Gemini) | `start` says it did not check it, never that it passed. |
230
+ | Logged out, usage limit, bad API key | The run fails fast. We retry once, after 15 minutes. A second failure in a row sends the issue to needs-human, with the agent's last lines in the comment and the notification. We never retry in a tight loop. |
231
+ | Codex refuses to start outside a git repository | A room's folder is deliberately not a git checkout. Its command needs `--skip-git-repo-check` (see "Which agents"). |
232
+ | The agent never joins its room (Hivecode isn't set up for that agent CLI) | Said as exactly that, with the setup command. Never reported as "no changes". |
233
+ | It runs forever: stuck, or waiting for a permission prompt in headless mode | A time limit per run (`--max-minutes`, default 40). We kill the whole process tree, push nothing, keep its folder for inspection, and mark the issue needs-human. |
234
+ | It finishes without changing anything | On a first run: no PR. We comment with its own last words and mark the issue needs-human. On a review run, a question answered needs no change: its answer is posted on the PR. |
235
+ | It has a question | Nobody answers during a run, and the task says so. It writes `BLOCKED: <question>` and stops. We post the question on the issue and notify. A reply from someone with write access starts the next run. |
236
+ | Its last edit didn't reach the room before it exited | Before landing, we reconnect its folder, under its own identity, to send anything still pending. Landing refuses to land part of a room (Q507). |
237
+ | It says the tests pass and they don't | With `--test`, we run them ourselves. A failure becomes the next run's task. Without `--test`, the PR body says the result is the agent's report, not ours. |
238
+ | The tester says nothing usable: no VERDICT, or "mostly passes" | Only `VERDICT: PASS` or `VERDICT: FAIL` counts. Anything else is a failed run, never a pass. It is retried once after 15 minutes, then goes to a person. |
239
+ | The tester fails the builder's work again and again | After three FAILs in a row it goes to a person, with the tester's last report. The run caps apply as well. |
240
+ | The tester edits the implementation, not just tests | It is told not to. If it does, the PR names those files under "Checked by", so a reviewer reads them with care. |
241
+ | The tester passes broken code | A PASS is the tester's word. The --test gate, then CI, still run after it, and a person reviews the PR. |
242
+ | It edits `.github/workflows/` | Refused unless `--allow-workflow-changes` is set. An agent misled by an issue could write a workflow that leaks the repo's secrets when CI runs. |
243
+ | It writes a key or token into a file | The room never carries a file with a key in it (hive-secrets.js), so the landing doesn't either. We also scan everything we post on GitHub. |
244
+ | A CI fix loop that never converges (a flaky test) | `--max-runs` per issue (default 6) and per day (default 20). If the same checks fail twice in a row, it goes to needs-human. |
245
+ | It pushes or opens a PR itself, bypassing the watcher | The task tells it not to. If it does anyway, nothing it pushed is overwritten: our push is refused, and the issue goes to a person. |
246
+ | It does damage outside the repo | On the user's own machine, an agent can do anything the user can. We say so plainly. To isolate it, run the watcher in a container or VM. |
247
+ | Prompt injection in an issue or a comment | Work starts only from a label someone with push access added. Only comments from people with write access are forwarded, checked against GitHub's permission for each person, not just their badge. The task quotes the issue as data from someone else. Agents can't merge, and a person reviews every PR. |
248
+
249
+ ### The room and the relay
250
+
251
+ | what goes wrong | what we do |
252
+ |---|---|
253
+ | The relay is unreachable, or cold-starting (about 50 s on Render free) | The run doesn't start. We retry, and status says why. |
254
+ | The relay restarts mid-run and loses the room | The agent's folder still has its files, and its server refills the room when it reconnects. Landing checks that every file arrived (Q507). Worst case, we redo the run from the branch. |
255
+ | The public relay holds the code | Room ids are random (104 bits) and never posted on GitHub. `start` warns once when the repo is private and the relay is ours. Recommended: run your own relay (`npx hivecode-relay`). |
256
+ | A repo too big for a room | Rooms skip binaries and files over 1 MB, and agents on the watcher's machine fill those in from git. Past 5,000 files on the public relay, `start` refuses (Q608) and offers two ways out: your own relay, or `--allow-large`. |
257
+
258
+ ### The machine
259
+
260
+ | what goes wrong | what we do |
261
+ |---|---|
262
+ | The laptop sleeps | Everything pauses. On wake we push each deadline back by the time asleep, so a sleeping run is not killed for being slow. For 24/7, run on a machine that stays on. |
263
+ | The watcher crashes, or Ctrl+C | It kills its agents' process trees on the way out. On restart, a run marked running whose process is gone counts as a failed run and is retried. |
264
+ | Windows | Agent CLIs are `.cmd` shims, so we start them through the shell. The prompt goes on stdin, never on the command line, so no quoting can break it. Process trees are killed with `taskkill /T`. |
265
+ | git asks for a password, or a credential manager opens a login window, and nobody is there to answer | Git is never allowed to prompt (`GIT_TERMINAL_PROMPT=0`, `GCM_INTERACTIVE=never`), so it fails at once instead of hanging. The error says to run `gh auth setup-git`, and the issue goes to a person rather than retrying every two minutes. |
266
+ | The home folder is itself a git repository, and `start` is run outside the project | `start` says exactly that, instead of a puzzling "origin (none)". |
267
+ | The repo has its own git hooks (husky, lefthook, a tool's post-checkout hook) | Hivecode's own scratch worktrees run without them: landing, the catch-up merge and the test gate. A hook that failed there made landing fail although git had done its job, and others run installs in a folder about to be deleted. The developer's checkout and every push keep their hooks. |
268
+ | The disk fills with room folders | Finished issues' folders are removed. Only the last failed run's folder is kept. |
269
+
270
+ ### People
271
+
272
+ | what goes wrong | what we do |
273
+ |---|---|
274
+ | They comment while a run is going | It waits for the next run. One run per issue at a time. |
275
+ | They merge while a fix run is going | We kill the run, push nothing, and finish. |
276
+ | They want everything stopped | Remove the label, comment `/hivecode stop`, or Ctrl+C the watcher. |
277
+ | Notifications become noise | One per change of state, never one per comment. |
278
+ | They write "thanks" or "LGTM" on the PR | Nothing runs. Only reviews, inline comments, and comments starting with `/hivecode` go to the agent. |
279
+ | They can't approve the PR (it's theirs, under branch protection) | Said in the docs. The GitHub App fixes it. |
280
+ | A squash merge | GitHub keeps co-authors in the squash commit. The per-agent commits stay visible in the PR. |
281
+
282
+ ### When real users arrive
283
+
284
+ | what goes wrong | what we do |
285
+ |---|---|
286
+ | A stranger's private code on our public relay | The warning above, plus `--relay` in the first line of the docs. |
287
+ | Their tokens get spent without them | Runs only start from a label. Every run is capped and counted, and `github status` shows today's runs. |
288
+ | A support question | Every refusal has a Q-code and the exact command that fixes it. |
289
+ | Mixed versions | The agent's own Hivecode must know branch rooms (`hive_join` with `branch`). Landing needs signed receipts, which every such runtime has. |
290
+ | Mac, Linux and Windows | The tests run on all three, through the existing Mac/Linux workflow. |
291
+
292
+ ## Defaults
293
+
294
+ | setting | default |
295
+ |---|---|
296
+ | `--label` | `hivecode` |
297
+ | `--every` | 60 s |
298
+ | `--max-parallel` | 1 issue |
299
+ | `--max-minutes` | 40 per run |
300
+ | `--max-runs` | 6 per issue, 20 per day |
301
+ | `--relay` | the public relay; self-hosted recommended for private code |
302
+ | `--test` | off; the PR says the test result is the agent's report |
303
+ | `--repo` | the folder it is started in |
304
+ | `--dry-run` | off |
305
+ | `--slack-channel` | off; the token comes from `SLACK_BOT_TOKEN` |
306
+
307
+ ## Build order
308
+
309
+ All built on 13 Oct 2026:
310
+ 1. The watcher core: claim, run, land, PR, with caps and state.
311
+ 2. The loop: CI results and trusted comments become the next run.
312
+ 3. Kill switches and needs-human.
313
+ 4. Notifications.
314
+ 5. `github status`.
315
+ 6. Docs, plus this file kept true.
316
+
317
+ ## How it is tested
318
+
319
+ - `tests/github-watcher-test.mjs` runs everything for real except GitHub and the agent: a local
320
+ relay, a real git repo with a local `origin`, a fake GitHub inside the test, and a fake agent that
321
+ joins its room through the real MCP server code. It spends no tokens and touches no real repo.
322
+ - `tests/github-cli-test.mjs` checks what a stranger meets first: each refusal and its fix, and the
323
+ dry run, both on a fresh clone and on one a watcher has worked in.
324
+ - `scripts/github-mutants.mjs` removes each important defence in turn, and its check has to go red.
325
+ A defence whose check stays green is not covered, whatever the test claims.
326
+
327
+ ## Not in this version
328
+
329
+ - More than one builder working the same issue at once. The tester runs after the builder, not
330
+ beside it.
331
+ - The GitHub App: bot identity, webhooks, Marketplace. It needs Ganesh's account.
332
+ - Agents resolving merge conflicts.
333
+ - One watcher for several repos.
334
+ - Always-on hosting, on a VM or Maritime. The watcher is the piece that would run there.