@openhands/extensions 0.16.0 → 0.18.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 (70) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/AGENTS.md +1 -0
  3. package/README.md +5 -2
  4. package/automations/README.md +78 -11
  5. package/automations/bundle-index.js +18 -0
  6. package/automations/catalog/github-agents-md-maintainer/manifest.json +96 -0
  7. package/automations/catalog/github-issue-to-pr/manifest.json +107 -0
  8. package/automations/catalog/github-pr-reviewer/manifest.json +24 -7
  9. package/automations/catalog/github-repo-monitor/manifest.json +29 -34
  10. package/automations/catalog/jira-issue-to-pr/manifest.json +3 -3
  11. package/automations/catalog/news-digest/manifest.json +76 -0
  12. package/automations/catalog-index.js +13 -7
  13. package/automations/catalog.schema.json +112 -3
  14. package/automations/index.d.ts +66 -1
  15. package/automations/index.js +14 -0
  16. package/automations/interface.json +6 -2
  17. package/automations/interface.schema.json +6 -2
  18. package/index.d.ts +1 -1
  19. package/index.js +1 -1
  20. package/integrations/catalog/posthog.json +41 -3
  21. package/integrations/catalog/sonarqube.json +78 -0
  22. package/integrations/catalog-index.js +65 -63
  23. package/marketplaces/openhands-extensions.json +49 -0
  24. package/package.json +1 -1
  25. package/plugins/qa-changes/action.yml +1 -1
  26. package/pyproject.toml +1 -1
  27. package/python/openhands_extensions/_version.py +1 -1
  28. package/scripts/build-automation-catalog.mjs +30 -0
  29. package/scripts/build-skills-catalog.mjs +24 -7
  30. package/skills/add-skill/SKILL.md +1 -1
  31. package/skills/github-agents-md-maintainer/.plugin/plugin.json +19 -0
  32. package/skills/github-agents-md-maintainer/README.md +59 -0
  33. package/skills/github-agents-md-maintainer/SKILL.md +271 -0
  34. package/skills/github-agents-md-maintainer/commands/agents-md-setup.md +8 -0
  35. package/skills/github-agents-md-maintainer/references/state-schema.md +113 -0
  36. package/skills/github-agents-md-maintainer/scripts/main.py +1187 -0
  37. package/skills/github-issue-to-pr/.plugin/plugin.json +19 -0
  38. package/skills/github-issue-to-pr/README.md +84 -0
  39. package/skills/github-issue-to-pr/SKILL.md +373 -0
  40. package/skills/github-issue-to-pr/commands/issue-to-pr-setup.md +8 -0
  41. package/skills/github-issue-to-pr/references/state-schema.md +139 -0
  42. package/skills/github-issue-to-pr/scripts/main.py +1305 -0
  43. package/skills/github-pr-reviewer/README.md +12 -6
  44. package/skills/github-pr-reviewer/SKILL.md +93 -31
  45. package/skills/github-pr-reviewer/commands/pr-reviewer-setup.md +1 -1
  46. package/skills/github-pr-reviewer/references/state-schema.md +68 -18
  47. package/skills/github-pr-reviewer/scripts/main.py +540 -103
  48. package/skills/github-pr-reviewer/tests/test_main.py +503 -0
  49. package/skills/index.d.ts +6 -0
  50. package/skills/index.js +56 -17
  51. package/skills/jira-issue-to-pr/scripts/main.py +4 -2
  52. package/skills/news-digest/.plugin/plugin.json +19 -0
  53. package/skills/news-digest/README.md +71 -0
  54. package/skills/news-digest/SKILL.md +284 -0
  55. package/skills/news-digest/commands/news-digest-setup.md +8 -0
  56. package/skills/news-digest/references/state-schema.md +178 -0
  57. package/skills/news-digest/scripts/main.py +1250 -0
  58. package/skills/openhands-sdk/SKILL.md +2 -0
  59. package/skills/skill-creator/SKILL.md +1 -1
  60. package/skills/slack-channel-monitor/scripts/main.py +3 -2
  61. package/tests/fixtures/automations/capabilities.json +30 -0
  62. package/tests/fixtures/automations/github-pr-reviewer.json +319 -55
  63. package/tests/fixtures/automations/github-repo-monitor.json +230 -62
  64. package/tests/test_automation_setup.py +260 -31
  65. package/tests/test_catalogs.py +46 -1
  66. package/tests/test_github_agents_md_maintainer.py +352 -0
  67. package/tests/test_github_issue_to_pr.py +539 -0
  68. package/tests/test_news_digest.py +646 -0
  69. package/tests/test_skills_catalog.py +182 -36
  70. package/tests/test_slack_channel_monitor.py +1 -1
@@ -12,25 +12,31 @@ This skill is activated by:
12
12
  ## Features
13
13
 
14
14
  - Reviews PRs on demand by watching for a GitHub label event
15
+ - Watches several repositories from a single automation, each with its own state
15
16
  - Processes each label application exactly once, with persistent state
16
17
  - Re-review support by removing and re-applying the label
17
18
  - Suppresses stale reviews when the PR head commit changes mid-review
18
- - Uses a real cloned checkout and full PR context instead of only a truncated diff
19
- - Posts acknowledgement and final review comments with AI disclosure
19
+ - Hands the agent the reviewed commit already checked out, and removes that
20
+ checkout when the review ends, so nothing accumulates between runs
21
+ - Publishes a real pull request review, with inline comments where a finding
22
+ maps to a changed line, and verifies on GitHub that it landed
23
+ - Posts acknowledgement comments with AI disclosure
20
24
  - Configurable review tone and polling schedule
21
25
 
22
26
  ## Prerequisites
23
27
 
24
28
  Set `GITHUB_PERSONAL_ACCESS_TOKEN` in OpenHands Settings -> Secrets. The token
25
- must be able to read the repository, read pull requests, read issue events, and
26
- write issue comments.
29
+ must be able to read the repositories and their contents, read issue events,
30
+ write issue comments, and **write pull request reviews** — the review is
31
+ published through the pull request reviews API, so read-only pull request access
32
+ is not enough.
27
33
 
28
34
  ## Quick Start
29
35
 
30
36
  Ask OpenHands:
31
37
 
32
- > "Set up a PR review automation for my `myorg/backend` repo using the
33
- > `openhands-review` label and concise reviews."
38
+ > "Set up a PR review automation for my `myorg/backend` and `myorg/frontend`
39
+ > repos using the `openhands-review` label and concise reviews."
34
40
 
35
41
  After setup, apply the configured label to a pull request to queue a review. To
36
42
  request another review later, remove and re-apply the label.
@@ -2,23 +2,30 @@
2
2
  name: github-pr-reviewer
3
3
  description: >
4
4
  Create an automation that reviews GitHub pull requests when a configurable
5
- trigger label is applied. Polls GitHub deterministically, starts one
6
- OpenHands review conversation per label event, inspects full repository and
7
- PR context, and posts the final review comment back to GitHub.
5
+ trigger label is applied. Polls one or more repositories deterministically,
6
+ starts one OpenHands review conversation per label event with the pull
7
+ request's head commit already checked out, and publishes the review to GitHub.
8
8
  triggers:
9
9
  - /pr-reviewer:setup
10
10
  ---
11
11
 
12
12
  # GitHub PR Reviewer Automation
13
13
 
14
- Create a cron automation that watches a GitHub repository for pull requests
15
- with a review trigger label, starts an OpenHands review conversation once per
16
- label event, and posts the AI review as a GitHub comment.
14
+ Create a cron automation that watches one or more GitHub repositories for pull
15
+ requests with a review trigger label, starts an OpenHands review conversation
16
+ once per label event, and publishes the AI review to GitHub.
17
17
  Windows PowerShell equivalents for the setup, packaging, upload, and API-check shell snippets are in `references/windows.md`.
18
18
 
19
19
  The automation script is deterministic: PR discovery, label-event tracking,
20
- state persistence, stale-result suppression, and GitHub comment posting are
21
- handled in Python. The LLM is invoked only for the review itself.
20
+ state persistence, stale-result suppression, the repository checkout, and its
21
+ removal are all handled in Python. The LLM is invoked only for the review
22
+ itself.
23
+
24
+ The script prepares each review's workspace before the agent starts: the pull
25
+ request's head commit is downloaded as a tarball and extracted to a directory of
26
+ its own, which becomes the conversation's working directory. The agent is told
27
+ not to clone, fetch, check out, or delete anything, and the script removes the
28
+ checkout once the conversation has stopped. Nothing accumulates between runs.
22
29
 
23
30
  ---
24
31
 
@@ -31,7 +38,13 @@ Verify that the following secret is set in **OpenHands Settings -> Secrets**:
31
38
  | Secret name | Token type | Minimum permissions |
32
39
  |---|---|---|
33
40
  | `GITHUB_PERSONAL_ACCESS_TOKEN` | Classic PAT | `repo` for private repos or `public_repo` for public repos |
34
- | `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Contents: Read, Metadata: Read, Pull requests: Read, Issues: Read and Write |
41
+ | `GITHUB_PERSONAL_ACCESS_TOKEN` | Fine-grained PAT | Contents: Read, Metadata: Read, Pull requests: **Read and Write**, Issues: Read and Write |
42
+
43
+ Pull-request **write** access is required because the agent publishes a pull
44
+ request review, not just an issue comment. A token with only Pull requests: Read
45
+ will poll happily and then fail at the point of publishing.
46
+
47
+ When several repositories are monitored, the token must cover all of them.
35
48
 
36
49
  Check with:
37
50
  ```bash
@@ -57,12 +70,13 @@ Run the `curl` check above.
57
70
  - If the API returns `{"message": "Bad credentials"}`: tell the user the
58
71
  token is invalid and ask them to update it. Stop.
59
72
 
60
- ### Step 2 - Collect repository
73
+ ### Step 2 - Collect repositories
61
74
 
62
- Ask: *"Which GitHub repository should be monitored?
63
- (Format: `owner/repo`, e.g. `myorg/backend`)"*
75
+ Ask: *"Which GitHub repositories should be monitored?
76
+ (Format: `owner/repo`, e.g. `myorg/backend`. List several separated by commas to
77
+ review them all from one automation.)"*
64
78
 
65
- Validate access:
79
+ Validate access to **each** repository:
66
80
  ```bash
67
81
  curl -s "https://api.github.com/repos/{owner}/{repo}" \
68
82
  -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
@@ -76,7 +90,13 @@ else:
76
90
  "
77
91
  ```
78
92
 
79
- Record `REPO = "{owner}/{repo}"`.
93
+ Record every accepted repository into `REPOS = ["{owner}/{repo}", ...]`. If one
94
+ repository fails the check, say which and ask whether to continue without it.
95
+
96
+ Each repository is polled independently and keeps its own state, so pull-request
97
+ numbers never collide between them. The trigger label, tone, and schedule are
98
+ shared by all of them; a repository needing different settings wants its own
99
+ automation.
80
100
 
81
101
  ### Step 3 - Collect trigger label
82
102
 
@@ -122,9 +142,15 @@ Record as `CRON_SCHEDULE`.
122
142
  Read `scripts/main.py` from this skill's directory. Apply exactly five constant
123
143
  substitutions near the top of the file:
124
144
 
145
+ > The script also reads a `config.json` shipped beside it, if there is one, over
146
+ > these constants. That is how the catalog entry
147
+ > (`automations/catalog/github-pr-reviewer/`) configures an unmodified copy,
148
+ > since a declarative host cannot rewrite Python. This setup path substitutes the
149
+ > constants and ships no `config.json`, so the two never collide.
150
+
125
151
  | Placeholder | Replace with |
126
152
  |---|---|
127
- | `REPO = "owner/repo"` | `REPO = "{owner_repo}"` |
153
+ | `REPOS = ["owner/repo"]` | `REPOS = ["{owner_repo}", ...]` - one entry per repository collected in Step 2 |
128
154
  | `TRIGGER_LABEL = "openhands-review"` | `TRIGGER_LABEL = "{trigger_label}"` |
129
155
  | `REVIEW_TONE = "thorough"` | `REVIEW_TONE = "{review_tone}"` |
130
156
  | `REVIEW_STYLE_INSTRUCTIONS = ""` | `REVIEW_STYLE_INSTRUCTIONS = "{style_instructions}"` |
@@ -132,6 +158,7 @@ substitutions near the top of the file:
132
158
 
133
159
  Use a safe string writer such as `json.dumps(value)` when inserting user-provided
134
160
  repository names, labels, or style instructions into Python string literals.
161
+ `json.dumps(list_of_repos)` produces the whole `REPOS` list safely in one step.
135
162
 
136
163
  Write the customized script to a temporary build directory:
137
164
  ```bash
@@ -173,14 +200,19 @@ curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1" \
173
200
  -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
174
201
  -H "Content-Type: application/json" \
175
202
  -d "{
176
- \"name\": \"GitHub PR Reviewer: {owner}/{repo} label {trigger_label}\",
203
+ \"name\": \"GitHub PR Reviewer: {repo_summary} label {trigger_label}\",
177
204
  \"trigger\": {\"type\": \"cron\", \"schedule\": \"{cron_schedule}\"},
178
205
  \"tarball_path\": \"$TARBALL_PATH\",
179
206
  \"entrypoint\": \"python3 main.py\",
180
- \"timeout\": 300
207
+ \"timeout\": 600
181
208
  }" | python3 -m json.tool
182
209
  ```
183
210
 
211
+ Use the single repository as `{repo_summary}` when there is one, and something
212
+ like `3 repos` when there are several. A poll now downloads a tarball per queued
213
+ review, so the timeout allows for that; a run never waits for a review to
214
+ finish, only for it to be started.
215
+
184
216
  Record the returned `id`.
185
217
 
186
218
  ### Step 9 - Confirm
@@ -190,44 +222,67 @@ Tell the user:
190
222
  > ✅ **GitHub PR Reviewer** is running!
191
223
  >
192
224
  > - Automation ID: `{id}`
193
- > - Repository: `{owner}/{repo}`
225
+ > - Repositories: `{owner}/{repo}`, ... (one line each)
194
226
  > - Trigger label: `{trigger_label}`
195
227
  > - Review tone: `{tone}`
196
228
  > - Polling schedule: `{cron_schedule}`
197
- > - State file: `~/.openhands/workspaces/automation-state/github_pr_reviewer_label_event_{id}.json`
229
+ > - State file per repository:
230
+ > `~/.openhands/workspaces/automation-state/github_pr_reviewer_label_event_{id}_{owner}__{repo}.json`
198
231
  >
199
232
  > Apply the `{trigger_label}` label to a pull request to queue a review. Each
200
233
  > label event is processed once. To request another review, remove and re-apply
201
234
  > the label.
235
+ >
236
+ > The review is published as a pull request review on the head commit, with
237
+ > inline comments where a finding maps to a changed line.
202
238
 
203
239
  ---
204
240
 
205
241
  ## Runtime Behaviour (per poll)
206
242
 
207
- Each cron run executes `main.py`, which:
243
+ Each cron run executes `main.py`, which resolves and validates
244
+ `GITHUB_PERSONAL_ACCESS_TOKEN` once, then processes every repository in `REPOS`
245
+ independently. One repository failing does not stop the others; the run fails
246
+ only if every repository fails.
247
+
248
+ For each repository:
208
249
 
209
- 1. Loads state from the JSON file (see `references/state-schema.md`).
210
- 2. Resolves and validates `GITHUB_PERSONAL_ACCESS_TOKEN` and repository access.
250
+ 1. Loads that repository's state (see `references/state-schema.md`).
251
+ 2. Verifies repository access.
211
252
  3. Lists open PRs, newest-updated first.
212
253
  4. For each open PR carrying `TRIGGER_LABEL`:
213
254
  - Refetches current PR metadata to avoid acting on stale list data.
214
255
  - Finds the latest matching GitHub `labeled` issue event.
215
256
  - Skips the event if it has already been tracked.
216
- - Starts an OpenHands conversation with a review prompt that includes PR
217
- metadata, the exact head SHA, label event details, and instructions to
218
- clone the repo, inspect PR discussion, review comments, changed files,
219
- diff, and surrounding code.
257
+ - Downloads the PR's head commit as a tarball and extracts it to
258
+ `{WORKSPACE_BASE}/repositories/{owner}__{repo}/pr-{number}-{sha12}`. The
259
+ archive is checked as it is unpacked: a single root, no absolute or `..`
260
+ paths, and symlinks skipped rather than materialised.
261
+ - Starts an OpenHands conversation **whose working directory is that
262
+ checkout**, with a review prompt carrying PR metadata, the exact head SHA,
263
+ and label event details.
220
264
  - Posts an acknowledgement comment with the label event, head SHA, and
221
265
  conversation link.
222
- - Records the label-event review in state with `status: "active"`.
266
+ - Records the review in state with `status: "active"` and the checkout path.
267
+ - If the checkout or the conversation cannot be created, the checkout is
268
+ removed and nothing is recorded, so the next poll retries the label event.
223
269
  5. For each active review conversation:
224
270
  - Marks it closed without posting if the PR has closed or merged.
225
271
  - Suppresses stale results if the PR head SHA changed after the review was
226
272
  queued.
227
273
  - When the conversation reaches `idle`, `finished`, `error`, or `stuck`,
228
- posts the agent's final response as a GitHub comment and marks the review
229
- closed.
230
- 6. Saves state atomically and fires the completion callback.
274
+ asks GitHub whether a review by the token's own user exists for that head
275
+ SHA. If it does, the review is complete. If it does not, the agent's final
276
+ response is posted as a comment so the work is not lost.
277
+ - Abandons a conversation that has not reached a terminal status within two
278
+ hours, so its checkout can be reclaimed.
279
+ 6. Removes the checkout of every finished review, but only after confirming the
280
+ conversation has stopped - deleting it under a running agent would remove its
281
+ working directory. When that cannot be confirmed the directory is left alone
282
+ and the next poll tries again.
283
+ 7. Saves that repository's state atomically.
284
+
285
+ The completion callback fires once for the whole run.
231
286
 
232
287
  ---
233
288
 
@@ -237,6 +292,9 @@ Each cron run executes `main.py`, which:
237
292
  review lifecycle diagram.
238
293
  - **`scripts/main.py`** - The complete automation script. Customize the five
239
294
  constants at the top before packaging.
295
+ - **`tests/test_main.py`** - Unit tests for the checkout, its removal, and state
296
+ handling. Run them from the skill root with `python -m pytest tests/` after
297
+ editing the script.
240
298
 
241
299
  ---
242
300
 
@@ -246,7 +304,11 @@ Each cron run executes `main.py`, which:
246
304
  |---|---|---|
247
305
  | Bot never queues reviews | Trigger label not present or no matching `labeled` event | Apply the configured label to the PR |
248
306
  | "Bad credentials" in run logs | Token expired | Rotate and update `GITHUB_PERSONAL_ACCESS_TOKEN` |
249
- | 404 on repo access | Repo name wrong or no access | Re-check `owner/repo` and token permissions |
307
+ | 404 on repo access | Repo name wrong or no access | Re-check the entry in `REPOS` and the token's permissions |
308
+ | One repository is skipped, others work | That repository failed its access check | Read the `=== owner/repo ===` block in the run log |
250
309
  | Same PR not reviewed after new commits | Label event was already processed | Remove and re-apply the trigger label |
251
310
  | Review result never posts | Conversation still running or stuck | Open the conversation link from the acknowledgement comment |
252
311
  | Stale review suppressed | PR head SHA changed while the agent was reviewing | Re-apply the trigger label after the latest commit |
312
+ | Review arrives as a plain comment, not a review | Publishing failed, so the script posted the text as a fallback | Check that the token has Pull requests: Read and Write |
313
+ | Agent reports it cannot clone the repo | Prompt asked it not to; the workspace is already the checkout | No action - the code is at the head SHA in its working directory |
314
+ | Checkouts remain under `repositories/` | Their conversations had not stopped yet | They are removed by a later poll once the conversation is terminal |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  # auto-generated by sync_extensions.py
3
- description: Create an automation that reviews GitHub pull requests when a configurable trigger label is applied. Polls GitHub deterministically, starts one OpenHands review conversation per label event, inspects full repository and PR context, and posts the final review comment back to GitHub.
3
+ description: Create an automation that reviews GitHub pull requests when a configurable trigger label is applied. Polls one or more repositories deterministically, starts one OpenHands review conversation per label event with the pull request's head commit already checked out, and publishes the review to GitHub.
4
4
  ---
5
5
 
6
6
  Read and follow the complete instructions in the SKILL.md file located in this skill's directory.
@@ -1,22 +1,28 @@
1
1
  # State Schema
2
2
 
3
- The automation maintains a JSON state document that persists across polling runs.
4
- It is the source of truth for which trigger-label events have queued reviews
5
- and which conversations are still active.
3
+ The automation maintains a JSON state document **per repository**, persisted
4
+ across polling runs. It is the source of truth for which trigger-label events
5
+ have queued reviews, which conversations are still active, and which repository
6
+ checkouts are still on disk.
7
+
8
+ Each repository in `REPOS` gets its own document, so pull-request numbers from
9
+ different repositories never share a bucket.
6
10
 
7
11
  ---
8
12
 
9
13
  ## Storage
10
14
 
11
15
  **Primary (cloud):** The state is stored in the automation service's built-in KV
12
- store under the key `"state"`. The KV store is available when `AUTOMATION_KV_TOKEN`
13
- is injected into the run environment. Each automation has its own isolated namespace.
16
+ store under the key `state:{owner}__{repo}` — for example
17
+ `state:OpenHands__extensions`. The KV store is available when
18
+ `AUTOMATION_KV_TOKEN` is injected into the run environment. Each automation has
19
+ its own isolated namespace.
14
20
 
15
- **Fallback (local/dev):** When the KV store is not available, the state is written
16
- to a local JSON file at:
21
+ **Fallback (local/dev):** When the KV store is not available, the state is
22
+ written to a local JSON file at:
17
23
 
18
24
  ```
19
- {WORKSPACE_BASE_ROOT}/automation-state/github_pr_reviewer_label_event_{automation_id}.json
25
+ {WORKSPACE_BASE_ROOT}/automation-state/github_pr_reviewer_label_event_{automation_id}_{owner}__{repo}.json
20
26
  ```
21
27
 
22
28
  `WORKSPACE_BASE_ROOT` is derived by going two levels up from the `WORKSPACE_BASE`
@@ -25,19 +31,28 @@ environment variable, stripping `automation-runs/{run_id}`.
25
31
  Example on a local install:
26
32
 
27
33
  ```
28
- ~/.openhands/workspaces/automation-state/github_pr_reviewer_label_event_abc12345-....json
34
+ ~/.openhands/workspaces/automation-state/github_pr_reviewer_label_event_abc12345-..._myorg__backend.json
29
35
  ```
30
36
 
31
37
  The `automation_id` is read from the `AUTOMATION_EVENT_PAYLOAD` environment
32
38
  variable, field `automation_id`.
33
39
 
40
+ ### Upgrading from a single-repository automation
41
+
42
+ Earlier versions stored one document under the bare key `state` (or a filename
43
+ without the repository suffix). On the first poll after an upgrade, that document
44
+ is adopted for the repository named in its own `repo` field, and written back
45
+ under the new per-repository key. Reviews already handled are therefore not
46
+ re-run. A repository that does not match the legacy document simply starts with
47
+ empty state.
48
+
34
49
  ---
35
50
 
36
51
  ## Top-Level Schema
37
52
 
38
53
  ```jsonc
39
54
  {
40
- "version": 2,
55
+ "version": 3,
41
56
  "repo": "owner/repo",
42
57
  "trigger_label": "openhands-review",
43
58
  "updated_at": 1717200000.0,
@@ -65,6 +80,7 @@ Value: **ReviewRecord**
65
80
  "html_url": "https://github.com/owner/repo/pull/42",
66
81
  "status": "active",
67
82
  "conversation_id": "550e8400-e29b-41d4-a716-446655440000",
83
+ "workspace_dir": "/workspace/repositories/owner__repo/pr-42-0123456789ab",
68
84
  "last_activity": 1717200000.0
69
85
  }
70
86
  ```
@@ -73,12 +89,28 @@ Value: **ReviewRecord**
73
89
 
74
90
  | Status | Meaning |
75
91
  |---|---|
92
+ | `starting` | The label event is claimed and the checkout/conversation is being set up. Written and saved before that work begins, so a poll overlapping the claiming one skips this event instead of reviewing the same commit twice. Becomes `active` once the conversation exists, or the record is deleted if setup fails, so the next poll retries. A `starting` record older than `STALLED_CLAIM_SECONDS` (15 min) belongs to a poll that died mid-setup and is released. |
76
93
  | `active` | Review conversation is running or waiting to be collected |
77
- | `closed` | Final result was posted, or the PR closed before collection |
94
+ | `closed` | Final result was handled, or the PR closed before collection |
78
95
  | `stale` | PR head SHA changed before the review completed, so the result was suppressed |
96
+ | `expired` | Conversation never reached a terminal status within `MAX_ACTIVE_AGE` (2 h) and was abandoned |
79
97
 
80
98
  When a review becomes stale, `stale_reason` records the old and new head SHAs.
81
99
  When a review closes after posting, `completed_at` records the completion time.
100
+ When a review expires, `expired_after` records how many seconds it had been
101
+ waiting.
102
+
103
+ ### `workspace_dir`
104
+
105
+ The directory holding the reviewed commit, created by the script before the
106
+ conversation starts and used as that conversation's working directory. It is
107
+ removed once the conversation is confirmed stopped, and the key is deleted from
108
+ the record at the same time.
109
+
110
+ The key therefore doubles as the "still on disk" marker: a record carrying a
111
+ `workspace_dir` after it has left `active` is retried on every later poll until
112
+ the removal succeeds. A checkout is never removed while its conversation is
113
+ still running, and never outside `{WORKSPACE_BASE}/repositories/`.
82
114
 
83
115
  ---
84
116
 
@@ -108,14 +140,29 @@ because the trigger label was absent.
108
140
  Trigger label applied on GitHub
109
141
  |
110
142
  v
111
- [active] - conversation created, acknowledgement comment posted
143
+ [starting] - label event claimed and saved, before any slow work
144
+ |
145
+ +-- setup fails ----------------------------> record deleted, retried next poll
146
+ |
147
+ +-- claiming poll dies mid-setup -----------> released after 15 min
148
+ |
149
+ v
150
+ checkout prepared at head SHA, conversation created
151
+ |
152
+ v
153
+ [active] - acknowledgement comment posted
112
154
  |
113
- +-- PR closes/merges before collection --> [closed] without posting
155
+ +-- PR closes/merges before collection ------> [closed] without posting
114
156
  |
115
- +-- PR head SHA changes before collection --> [stale] without posting
157
+ +-- PR head SHA changes before collection ---> [stale] without posting
158
+ |
159
+ +-- no terminal status within 2 h -----------> [expired] without posting
160
+ |
161
+ v
162
+ [closed] - review confirmed on GitHub, or the agent's text posted as a comment
116
163
  |
117
164
  v
118
- [closed] - final review comment posted
165
+ checkout removed once the conversation has stopped
119
166
  ```
120
167
 
121
168
  ---
@@ -123,18 +170,21 @@ Trigger label applied on GitHub
123
170
  ## Resetting State
124
171
 
125
172
  To force the automation to reconsider previous label events, delete the state
126
- from the KV store (cloud) or the fallback file (local).
173
+ for that repository from the KV store (cloud) or the fallback file (local).
127
174
 
128
175
  **Cloud (KV store):**
129
176
  ```bash
130
- curl -X DELETE "${OPENHANDS_HOST}/api/automation/v1/kv/state" \
177
+ curl -X DELETE "${OPENHANDS_HOST}/api/automation/v1/kv/state:owner__repo" \
131
178
  -H "Authorization: Bearer ${AUTOMATION_KV_TOKEN}"
132
179
  ```
133
180
 
134
181
  **Local (file fallback):**
135
182
  ```bash
136
- rm ~/.openhands/workspaces/automation-state/github_pr_reviewer_label_event_<id>.json
183
+ rm ~/.openhands/workspaces/automation-state/github_pr_reviewer_label_event_<id>_owner__repo.json
137
184
  ```
138
185
 
186
+ Resetting state also forgets which checkouts are outstanding, so remove any
187
+ leftover directories under `{WORKSPACE_BASE}/repositories/owner__repo/` yourself.
188
+
139
189
  Usually, prefer removing and re-applying the trigger label. That preserves
140
190
  history while creating a new review request.