@openhands/extensions 0.8.0 → 0.9.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 (71) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/README.md +3 -2
  3. package/automations/catalog/jira-issue-to-pr.json +14 -0
  4. package/automations/index.js +2 -0
  5. package/integrations/catalog/atlassian.json +78 -3
  6. package/integrations/catalog/datadog.json +33 -2
  7. package/integrations/catalog/superhuman-mail.json +36 -0
  8. package/integrations/catalog-index.js +45 -43
  9. package/marketplaces/openhands-extensions.json +10 -0
  10. package/package.json +1 -1
  11. package/plugins/cobol-modernization/skills/build-setup/SKILL.md +1 -0
  12. package/plugins/cobol-modernization/skills/cobol-modernization/SKILL.md +9 -0
  13. package/plugins/migration-scoring/skills/migration-scoring/SKILL.md +11 -0
  14. package/plugins/onboarding/skills/agent-readiness-report/SKILL.md +2 -0
  15. package/plugins/openhands/SKILL.md +7 -0
  16. package/plugins/release-notes/SKILL.md +9 -0
  17. package/pyproject.toml +1 -1
  18. package/python/openhands_extensions/_version.py +1 -1
  19. package/scripts/build-skills-catalog.mjs +1 -1
  20. package/skills/add-skill/SKILL.md +2 -0
  21. package/skills/azure-devops/SKILL.md +12 -1
  22. package/skills/bitbucket-cloud/SKILL.md +11 -0
  23. package/skills/code-review/SKILL.md +1 -0
  24. package/skills/datadog/SKILL.md +3 -1
  25. package/skills/datadog/references/windows.md +45 -0
  26. package/skills/discord/SKILL.md +2 -0
  27. package/skills/docker/SKILL.md +5 -1
  28. package/skills/github/SKILL.md +3 -1
  29. package/skills/github/references/windows.md +48 -0
  30. package/skills/github-pr-review/SKILL.md +1 -0
  31. package/skills/github-pr-review/references/windows.md +47 -0
  32. package/skills/github-pr-reviewer/SKILL.md +1 -0
  33. package/skills/github-pr-reviewer/references/windows.md +61 -0
  34. package/skills/github-repo-monitor/SKILL.md +1 -0
  35. package/skills/github-repo-monitor/references/windows.md +61 -0
  36. package/skills/gitlab/SKILL.md +3 -1
  37. package/skills/incident-retrospective/SKILL.md +2 -0
  38. package/skills/index.js +36 -30
  39. package/skills/iterate/SKILL.md +1 -0
  40. package/skills/iterate/references/windows.md +41 -0
  41. package/skills/jira-issue-to-pr/.plugin/plugin.json +19 -0
  42. package/skills/jira-issue-to-pr/README.md +56 -0
  43. package/skills/jira-issue-to-pr/SKILL.md +216 -0
  44. package/skills/jira-issue-to-pr/references/setup.md +170 -0
  45. package/skills/jira-issue-to-pr/scripts/main.py +341 -0
  46. package/skills/jupyter/SKILL.md +7 -1
  47. package/skills/kubernetes/SKILL.md +2 -1
  48. package/skills/kubernetes/references/windows.md +38 -0
  49. package/skills/linear/SKILL.md +2 -0
  50. package/skills/linear/references/windows.md +29 -0
  51. package/skills/linear-triage/SKILL.md +2 -0
  52. package/skills/notion/SKILL.md +2 -0
  53. package/skills/notion/references/windows.md +43 -0
  54. package/skills/openhands-api/SKILL.md +1 -0
  55. package/skills/openhands-api/references/windows.md +44 -0
  56. package/skills/openhands-automation/SKILL.md +1 -0
  57. package/skills/openhands-automation/references/custom-automation.md +20 -0
  58. package/skills/openhands-automation/references/windows.md +57 -0
  59. package/skills/pdflatex/SKILL.md +3 -1
  60. package/skills/research-brief/SKILL.md +2 -0
  61. package/skills/skill-creator/SKILL.md +1 -0
  62. package/skills/skill-creator/references/windows.md +30 -0
  63. package/skills/slack-channel-monitor/SKILL.md +1 -0
  64. package/skills/slack-channel-monitor/references/windows.md +64 -0
  65. package/skills/slack-standup-digest/SKILL.md +2 -0
  66. package/skills/spark-version-upgrade/SKILL.md +2 -1
  67. package/skills/ssh/SKILL.md +2 -1
  68. package/skills/ssh/references/windows.md +59 -0
  69. package/skills/swift-linux/SKILL.md +2 -1
  70. package/skills/vercel/SKILL.md +5 -0
  71. package/tests/test_catalogs.py +6 -1
@@ -21,6 +21,7 @@ No scripts — you are the orchestration loop. Uses only standard `gh` CLI
21
21
  commands that work on any GitHub repo.
22
22
 
23
23
  Requires: `gh` CLI authenticated with repo access, a PR branch.
24
+ Windows PowerShell equivalents for Bash-only assignment, redirection, and quoting patterns in this skill are in `references/windows.md`.
24
25
 
25
26
  ## Discover what the repo has
26
27
 
@@ -0,0 +1,41 @@
1
+ # Windows Notes for Iterate Commands
2
+
3
+ The `gh` CLI works on Windows, but a few snippets in the skill use Bash syntax.
4
+
5
+ ## Assign Command Output
6
+
7
+ ```powershell
8
+ $sha = gh pr view --json headRefOid --jq .headRefOid
9
+ gh run list --commit $sha --status failure --json databaseId,name,conclusion `
10
+ --jq '.[] | "\(.databaseId)\t\(.name)\t\(.conclusion)"'
11
+ ```
12
+
13
+ ## Redirect Stderr
14
+
15
+ Replace `2>/dev/null` with `2>$null`:
16
+
17
+ ```powershell
18
+ gh pr create --fill --draft 2>$null
19
+ ```
20
+
21
+ ## Multi-line GraphQL Queries
22
+
23
+ Use a here-string for GraphQL.
24
+
25
+ ```powershell
26
+ $query = @'
27
+ query($owner: String!, $repo: String!, $pr: Int!) {
28
+ repository(owner: $owner, name: $repo) {
29
+ pullRequest(number: $pr) {
30
+ reviewThreads(last: 100) { nodes { id isResolved path line } }
31
+ }
32
+ }
33
+ }
34
+ '@
35
+
36
+ gh api graphql -f query="$query" -f owner="{owner}" -f repo="{repo}" -F pr="{number}"
37
+ ```
38
+
39
+ ## Chained Commands
40
+
41
+ If `&&` is not supported by the installed shell, run each command separately and check `$LASTEXITCODE`.
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "jira-issue-to-pr",
3
+ "version": "1.0.0",
4
+ "description": "Deploy a cron-based OpenHands automation that watches a Jira Cloud project for issues labeled with a configurable label (default: create-pr) and spawns an agent conversation to create a GitHub pull request for each new issue found. The target GitHub repo is read from the ticket body - no repo parameter required at deploy time.",
5
+ "author": {
6
+ "name": "OpenHands",
7
+ "email": "contact@all-hands.dev"
8
+ },
9
+ "homepage": "https://github.com/OpenHands/extensions",
10
+ "repository": "https://github.com/OpenHands/extensions",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "jira",
14
+ "atlassian",
15
+ "github",
16
+ "pull-request",
17
+ "automation"
18
+ ]
19
+ }
@@ -0,0 +1,56 @@
1
+ # Jira Issue to GitHub PR
2
+
3
+ Deploy a cron-based OpenHands automation that polls a Jira Cloud project for
4
+ open issues carrying a configurable label (default: `create-pr`) and, for each
5
+ new issue, starts an OpenHands agent conversation that clones the GitHub
6
+ repository named in the ticket body, creates a branch, implements the requested
7
+ change, and opens a pull request. Once the conversation starts, it posts a
8
+ comment on the Jira ticket: "I'm on it: `<conversation URL>`".
9
+
10
+ ## Triggers
11
+
12
+ This skill is activated by keywords:
13
+
14
+ - `set up a Jira automation to create pull requests`
15
+ - `poll Jira for create-pr issues`
16
+ - `automatically create GitHub PRs from Jira tickets`
17
+ - `deploy a Jira issue-to-PR automation`
18
+ - `create a Jira to GitHub PR workflow`
19
+ - `automate GitHub PR creation from a Jira label`
20
+
21
+ ## How It Works
22
+
23
+ 1. **Poll** - every N minutes, the script calls `POST /rest/api/3/search/jql`
24
+ on the Jira Cloud instance to find open issues with the configured label.
25
+ 2. **Deduplicate** - on the first run the script records a `first_run_at`
26
+ baseline timestamp in the KV store; issues whose `updated` timestamp
27
+ predates that baseline are skipped (no backfill blast on first deploy).
28
+ Subsequent runs filter by both `first_run_at` and a KV-backed set of
29
+ already-processed issue keys. A `max_new_per_run` cap (default 5) limits
30
+ conversations started per cron firing.
31
+ 3. **Dispatch** - for each new issue, the script calls
32
+ `POST /api/conversations` on the agent server to start an independent agent
33
+ conversation with a PR-creation prompt. The prompt instructs the agent to
34
+ extract the target GitHub repository (`owner/repo`) from the ticket body.
35
+ 4. **Comment** - immediately after the conversation is created, the script
36
+ posts a Jira comment on the issue: `I'm on it: <conversation URL>`.
37
+ 5. **Persist** - the processed issue key is recorded so re-runs never
38
+ duplicate work.
39
+
40
+ The polling run is lightweight (Python stdlib only, no SDK install); LLM costs
41
+ are incurred only when new issues are actually found.
42
+
43
+ ## Prerequisites
44
+
45
+ | Requirement | Details |
46
+ |---|---|
47
+ | **Jira API token** | Stored as an OpenHands secret (see `references/setup.md`) |
48
+ | **GitHub token** | Stored as an OpenHands secret with `repo` + `workflow` scope so the spawned conversation can push branches and open PRs |
49
+ | **Jira label** | The label to watch for (default: `create-pr`) must exist in the Jira project |
50
+ | **GitHub repo** | The target repository must exist and the GitHub token must have write access |
51
+
52
+ ## Quick Start
53
+
54
+ See `SKILL.md` for the full step-by-step deployment guide, and
55
+ `references/setup.md` for Jira API token creation, GitHub token scopes, cron
56
+ schedule reference, and troubleshooting.
@@ -0,0 +1,216 @@
1
+ ---
2
+ name: jira-issue-to-pr
3
+ description: >
4
+ This skill should be used when the user asks to "set up a Jira automation to create pull requests",
5
+ "poll Jira for create-pr issues", "automatically create GitHub PRs from Jira tickets",
6
+ "deploy a Jira issue-to-PR automation", "create a Jira to GitHub PR workflow",
7
+ or mentions automating GitHub PR creation from a Jira label.
8
+ Deploys a cron-based OpenHands automation that watches a Jira Cloud project for issues
9
+ labeled with a configurable label (default: "create-pr") and spawns an agent conversation
10
+ to create a GitHub pull request for each new issue found. The target GitHub repository
11
+ is read from the body of the Jira ticket - no repo parameter is required at deploy time.
12
+ ---
13
+
14
+ # Jira → GitHub PR Automation
15
+
16
+ Deploys a cron automation that polls a Jira Cloud instance for open issues carrying a
17
+ configurable label and, for each new issue, starts an OpenHands agent conversation that
18
+ clones the GitHub repository specified in the ticket body, creates a branch, implements
19
+ or placeholders the requested change, and opens a pull request. Once the conversation
20
+ starts, it also posts a comment on the Jira ticket: "I'm on it: &lt;conversation URL&gt;".
21
+
22
+ ## How It Works
23
+
24
+ 1. **Poll** - every N minutes, `POST /rest/api/3/search/jql` on the Jira Cloud instance
25
+ to find open issues with the configured label.
26
+ 2. **Deduplicate** - on the very first run the script records a `first_run_at` baseline
27
+ timestamp in the KV store; any issue whose `updated` timestamp predates that baseline
28
+ is skipped (no backfill blast on first deploy). Using `updated` rather than `created`
29
+ means an old issue that has its label added after the automation is deployed will still
30
+ be picked up. Subsequent runs filter by both `first_run_at` and a KV-backed set of
31
+ already-processed issue keys. A `max_new_per_run` cap (default 5) limits conversations
32
+ started per cron firing as additional defense-in-depth.
33
+ 3. **Dispatch** - for each new issue, call `POST /api/conversations` on the agent server
34
+ to start an independent agent conversation with a PR-creation prompt. The prompt
35
+ instructs the agent to extract the target GitHub repository (`owner/repo`) from the
36
+ ticket body.
37
+ 4. **Comment** - immediately after the conversation is created, post a Jira comment on the
38
+ issue: `I'm on it: <conversation URL>`.
39
+ 5. **Persist** - record the processed issue key so re-runs never duplicate work.
40
+
41
+ The polling run is lightweight (stdlib only, no SDK install); LLM costs are incurred only
42
+ when new issues are actually found.
43
+
44
+ ## Prerequisites
45
+
46
+ Before deploying, ensure the following are in place:
47
+
48
+ | Requirement | Details |
49
+ |---|---|
50
+ | **Jira API token** | Stored as an OpenHands secret (see [Jira API token setup](#jira-api-token)) |
51
+ | **GitHub token** | Must be stored as an OpenHands secret with `repo` + `workflow` scope so the spawned conversation can push branches and open PRs |
52
+ | **Jira label** | The label to watch for (default: `create-pr`) must exist in the Jira project |
53
+ | **GitHub repo** | The target repository must exist and the GitHub token must have write access |
54
+
55
+ ## Deploying the Automation
56
+
57
+ ### Step 1 - Collect parameters
58
+
59
+ Gather the following from the user before proceeding:
60
+
61
+ | Parameter | Example | Notes |
62
+ |---|---|---|
63
+ | `jira_base_url` | `https://acme.atlassian.net` | No trailing slash |
64
+ | `jira_email` | `alice@acme.com` | Atlassian account email for Basic auth |
65
+ | `jira_token_secret` | `JIRA_CLOUD_KEY` | Name of the OpenHands secret holding the API token |
66
+ | `jira_label` | `create-pr` | Label to watch for (optional, defaults to `create-pr`) |
67
+ | `max_new_per_run` | `5` | Max conversations dispatched per cron firing (optional, defaults to `5`) |
68
+ | `cron_schedule` | `*/5 * * * *` | Polling frequency in cron syntax |
69
+
70
+ > **Note**: The GitHub repository is not configured here. Each Jira ticket body must include
71
+ > a reference to the target GitHub repo in `owner/repo` format (e.g. `acme-org/backend`).
72
+ > The spawned agent extracts it from the ticket text.
73
+
74
+ ### Step 2 - Create config.json
75
+
76
+ Create `config.json` next to `scripts/main.py` when packaging:
77
+
78
+ ```json
79
+ {
80
+ "jira_base_url": "https://acme.atlassian.net",
81
+ "jira_email": "alice@acme.com",
82
+ "jira_token_secret": "JIRA_CLOUD_KEY",
83
+ "jira_label": "create-pr",
84
+ "max_new_per_run": 5
85
+ }
86
+ ```
87
+
88
+ ### Step 3 - Package the tarball
89
+
90
+ Copy `scripts/main.py` from this skill and package it with the `config.json`:
91
+
92
+ ```bash
93
+ WORK=$(mktemp -d)
94
+ cp <skill-dir>/scripts/main.py "$WORK/main.py"
95
+ # write config.json into $WORK/config.json (see Step 2)
96
+ tar -czf /tmp/jira-issue-to-pr.tar.gz -C "$WORK" .
97
+ python3 -m py_compile "$WORK/main.py" # validate syntax before uploading
98
+ ```
99
+
100
+ ### Step 4 - Upload the tarball
101
+
102
+ ```bash
103
+ TARBALL_PATH=$(curl -s -X POST \
104
+ "http://localhost:8000/api/automation/v1/uploads?name=jira-issue-to-pr" \
105
+ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
106
+ -H "Content-Type: application/gzip" \
107
+ --data-binary @/tmp/jira-issue-to-pr.tar.gz \
108
+ | python3 -c "import sys,json; print(json.load(sys.stdin)['tarball_path'])")
109
+ ```
110
+
111
+ ### Step 5 - Create the automation
112
+
113
+ ```bash
114
+ curl -s -X POST "http://localhost:8000/api/automation/v1" \
115
+ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
116
+ -H "Content-Type: application/json" \
117
+ -d "{
118
+ \"name\": \"Jira issue-to-PR Poller\",
119
+ \"trigger\": {
120
+ \"type\": \"cron\",
121
+ \"schedule\": \"*/5 * * * *\",
122
+ \"timezone\": \"UTC\"
123
+ },
124
+ \"tarball_path\": \"$TARBALL_PATH\",
125
+ \"entrypoint\": \"python3 main.py\",
126
+ \"timeout\": 540
127
+ }" | python3 -m json.tool
128
+ ```
129
+
130
+ Save the returned `id` - use it for updates and monitoring.
131
+
132
+ ### Step 6 - Verify with a test dispatch
133
+
134
+ ```bash
135
+ curl -s -X POST \
136
+ "http://localhost:8000/api/automation/v1/<AUTOMATION_ID>/dispatch" \
137
+ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" | python3 -m json.tool
138
+
139
+ # After ~30 seconds, check the run status:
140
+ curl -s "http://localhost:8000/api/automation/v1/<AUTOMATION_ID>/runs?limit=1" \
141
+ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
142
+ | python3 -c "import sys,json; r=json.load(sys.stdin)['runs'][0]; print(r['status'], r.get('error_detail'))"
143
+ ```
144
+
145
+ ## Updating an Existing Deployment
146
+
147
+ To change configuration or update the script:
148
+
149
+ 1. Edit `config.json` with new values.
150
+ 2. Repackage and upload a new tarball (Steps 3-4 above).
151
+ 3. PATCH the existing automation with the new `tarball_path`:
152
+
153
+ ```bash
154
+ curl -s -X PATCH \
155
+ "http://localhost:8000/api/automation/v1/<AUTOMATION_ID>" \
156
+ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \
157
+ -H "Content-Type: application/json" \
158
+ -d "{\"tarball_path\": \"<NEW_TARBALL_PATH>\"}"
159
+ ```
160
+
161
+ ## Resetting Processed State
162
+
163
+ To reprocess issues that were already handled (e.g., after testing), clear the KV store:
164
+
165
+ ```bash
166
+ curl -s -X DELETE \
167
+ "http://localhost:8000/api/automation/v1/<KV_BASE>/v1/kv/state" \
168
+ -H "Authorization: Bearer $AUTOMATION_KV_TOKEN"
169
+ ```
170
+
171
+ Or delete and recreate the automation to start with a clean state.
172
+
173
+ ## Script Reference
174
+
175
+ The automation script lives at `scripts/main.py`. Key behaviors:
176
+
177
+ - **No SDK dependencies** - pure Python stdlib; no `setup.sh` or `uv` install needed.
178
+ - **Config file** - reads all parameters from `config.json` co-located with the script.
179
+ - **First-run baseline** - on the very first execution the script writes `first_run_at` (UTC timestamp) into the KV store and exits without dispatching; issues whose `updated` timestamp predates that baseline are skipped on all subsequent runs. Using `updated` (not `created`) means an old issue that has its label applied after deployment is correctly treated as new.
180
+ - **Per-run cap** - `max_new_per_run` (default 5) limits how many conversations are started per cron firing; any remaining new issues are dispatched on the next run.
181
+ - **KV store** - persists `{"processed_keys": [...], "first_run_at": "..."}` between runs; falls back to a local file in dev environments where `AUTOMATION_KV_TOKEN` is absent.
182
+ - **Jira API** - uses `POST /rest/api/3/search/jql` (the current non-deprecated endpoint).
183
+ - **Conversation dispatch** - calls `POST /api/conversations` on the agent server with the current user's LLM/agent settings forwarded to the new conversation.
184
+ - **Error transparency** - captures Jira HTTP response bodies in error messages for fast diagnosis.
185
+
186
+ ## Known Limitations
187
+
188
+ ### Pre-existing issues updated after deployment
189
+
190
+ The deduplication filter compares each issue's `fields.updated` timestamp against
191
+ `first_run_at`. `updated` is Jira's last-modified timestamp for the issue as a whole —
192
+ it advances whenever **any** field changes (comments, priority, description, status, etc.),
193
+ not only when the `create-pr` label is applied.
194
+
195
+ This means a pre-existing issue that already carried the label at deployment time can
196
+ slip through the filter if it is later updated for an unrelated reason (e.g. someone adds
197
+ a comment), because its `updated` timestamp will have advanced past `first_run_at` while
198
+ its key is not yet in `processed_keys`.
199
+
200
+ **Workaround:** The only fully reliable way to detect exactly when a label was applied
201
+ is the Jira changelog API (`GET /rest/api/3/issue/{key}/changelog`), which requires an
202
+ extra HTTP call per issue. To avoid that overhead, keep the automation's scope narrow:
203
+ use a label that is exclusively added as a PR-creation signal and is not already present
204
+ on issues at the time of deployment.
205
+
206
+ Once an issue is successfully dispatched its key is written to `processed_keys` in the
207
+ KV store and is **permanently skipped on every future run** — regardless of subsequent
208
+ label changes, comments, or any other updates to the issue. The only way to re-trigger a
209
+ previously processed issue is to manually clear the KV store or delete and recreate the
210
+ automation. This means the risk window described above is finite: as soon as the
211
+ automation processes a pre-existing issue (even accidentally), it will never dispatch
212
+ that issue again.
213
+
214
+ ## Additional Resources
215
+
216
+ - **`references/setup.md`** - Jira API token creation, GitHub token scopes, cron schedule reference, and troubleshooting guide.
@@ -0,0 +1,170 @@
1
+ # Setup & Troubleshooting Reference
2
+
3
+ ## Jira API Token
4
+
5
+ ### Create a token
6
+
7
+ 1. Log in to [id.atlassian.com](https://id.atlassian.com/manage-profile/security/api-tokens).
8
+ 2. Click **Create API token**.
9
+ 3. Give it a label (e.g., `openhands-create-pr`) and click **Create**.
10
+ 4. Copy the token value - it is shown only once.
11
+
12
+ ### Store the token as an OpenHands secret
13
+
14
+ Navigate to **Settings → Secrets** in the OpenHands UI and add a new secret:
15
+
16
+ | Field | Value |
17
+ |---|---|
18
+ | Name | `JIRA_CLOUD_KEY` (or any name; set `jira_token_secret` in `config.json` to match) |
19
+ | Value | The API token copied above |
20
+
21
+ ### How the script authenticates
22
+
23
+ The script constructs an HTTP Basic auth header:
24
+
25
+ ```
26
+ Authorization: Basic base64(<jira_email>:<jira_token>)
27
+ ```
28
+
29
+ The Jira Cloud REST API v3 requires this for all authenticated requests.
30
+
31
+ ---
32
+
33
+ ## GitHub Token
34
+
35
+ The spawned agent conversation clones the target repository, pushes a new branch, and
36
+ opens a pull request. The GitHub token must be stored as an OpenHands secret and must
37
+ have the following scopes:
38
+
39
+ | Scope | Required for |
40
+ |---|---|
41
+ | `repo` | Clone private repos, push branches, read/write PR content |
42
+ | `workflow` | Push changes that touch `.github/workflows/` files |
43
+
44
+ The token does **not** need to be referenced in `config.json` - the spawned conversation
45
+ inherits the user's configured secrets automatically through the agent server.
46
+
47
+ ### Store the token
48
+
49
+ Navigate to **Settings → Secrets** and store the token under the name `GITHUB_TOKEN`
50
+ (or whatever name is expected by the agent's GitHub skill).
51
+
52
+ ---
53
+
54
+ ## config.json Field Reference
55
+
56
+ | Field | Required | Default | Description |
57
+ |---|---|---|---|
58
+ | `jira_base_url` | ✅ | - | Full URL of your Atlassian instance, e.g. `https://acme.atlassian.net` |
59
+ | `jira_email` | ✅ | - | Email address of the Atlassian account that owns the API token |
60
+ | `jira_token_secret` | ❌ | `"JIRA_CLOUD_KEY"` | Name of the OpenHands secret holding the Jira API token |
61
+ | `jira_label` | ❌ | `"create-pr"` | Jira issue label to watch for |
62
+
63
+ > **GitHub repo**: not a config field. The target repository is read from the body of each
64
+ > Jira ticket. Ticket authors must include the repo in `owner/repo` format or as a full
65
+ > GitHub URL (e.g. `https://github.com/acme-org/backend`).
66
+
67
+ ---
68
+
69
+ ## Cron Schedule Reference
70
+
71
+ | Schedule | Meaning |
72
+ |---|---|
73
+ | `*/5 * * * *` | Every 5 minutes |
74
+ | `*/15 * * * *` | Every 15 minutes |
75
+ | `0 * * * *` | Every hour |
76
+ | `0 9 * * 1-5` | Weekdays at 9 AM UTC |
77
+ | `0 9,17 * * *` | Daily at 9 AM and 5 PM UTC |
78
+
79
+ For high-frequency schedules (tighter than 15 minutes), consider that each run with new
80
+ issues spawns one conversation per issue. Set `timeout` on the automation to something
81
+ less than the interval to avoid overlapping runs - the KV store prevents double-processing
82
+ even if runs do overlap.
83
+
84
+ ---
85
+
86
+ ## Jira Search API Notes
87
+
88
+ The script uses `POST /rest/api/3/search/jql` - the current non-deprecated Jira Cloud
89
+ search endpoint. All older endpoints return 410 Gone:
90
+
91
+ | Removed endpoint | Returns |
92
+ |---|---|
93
+ | `GET /rest/api/2/search` | 410 Gone |
94
+ | `GET /rest/api/3/search` | 410 Gone |
95
+ | `POST /rest/api/3/search` | 410 Gone |
96
+
97
+ Reference: [Atlassian changelog CHANGE-2046](https://developer.atlassian.com/changelog/#CHANGE-2046)
98
+
99
+ The `POST /rest/api/3/search/jql` request body:
100
+
101
+ ```json
102
+ {
103
+ "jql": "labels = \"create-pr\" AND statusCategory != Done",
104
+ "fields": ["key", "summary", "description", "status"],
105
+ "maxResults": 50
106
+ }
107
+ ```
108
+
109
+ The response is `SearchAndReconcileResults`; the script reads the `issues` array from it.
110
+
111
+ ---
112
+
113
+ ## Troubleshooting
114
+
115
+ ### `Jira search failed 401`
116
+
117
+ - Wrong email in `jira_email` - must match the Atlassian account that owns the token.
118
+ - Token expired or revoked - create a new one at id.atlassian.com and update the secret.
119
+ - Token copied with extra whitespace - secrets are stored/retrieved with `.strip()`.
120
+
121
+ ### `Jira search failed 403`
122
+
123
+ - The Atlassian account does not have **Browse Projects** permission on the target project.
124
+ - Grant the account project-level read access in Jira's project settings.
125
+
126
+ ### `Jira search failed 400` with `"The value 'xxx' does not exist for the field 'labels'"`
127
+
128
+ - The label does not exist in Jira yet. Create at least one issue with that label first,
129
+ or create the label explicitly in **Jira Settings → Issues → Labels**.
130
+
131
+ ### Run completes but no conversation is started
132
+
133
+ - All matching issues are already in `processed_keys`. Clear state or add a new issue.
134
+ - The Jira project has issues with the label but all are in a Done status category.
135
+ The JQL filter is `statusCategory != Done` - reopen or create a fresh issue.
136
+
137
+ ### Conversation started but no PR appears
138
+
139
+ - The spawned conversation inherits the user's agent/LLM settings. Check the conversation
140
+ in the OpenHands UI (it appears in the conversations list under the automation run).
141
+ - The GitHub token may lack `repo` scope to push or open PRs.
142
+ - The target repo's default branch may be protected; the token must have bypass permission,
143
+ or the PR should be created against a different base branch.
144
+
145
+ ### `fire_callback error` in output
146
+
147
+ - Non-fatal. The automation service callback URL is unavailable (common in local dev).
148
+ This does not affect conversation dispatch.
149
+
150
+ ### KV store not available (state falls back to local file)
151
+
152
+ - `AUTOMATION_KV_TOKEN` is not injected, meaning the deployment is not configured with a
153
+ KV secret. State is written to a local file under `$WORKSPACE_BASE/../automation-state/`.
154
+ This is lost between runs in cloud pod deployments - configure the KV store secret on
155
+ the automation service to get persistent state.
156
+
157
+ ---
158
+
159
+ ## Multiple Deployments
160
+
161
+ To watch multiple Jira projects or multiple GitHub repos, deploy separate automations —
162
+ one per `config.json`. Each automation has its own KV-store namespace (keyed by
163
+ automation ID), so processed-key sets do not cross-contaminate.
164
+
165
+ Example: two automations, one per project:
166
+
167
+ | Automation name | `jira_base_url` | `jira_label` | GitHub repo (in ticket body) |
168
+ |---|---|---|---|
169
+ | `Jira issue-to-PR - frontend` | `https://acme.atlassian.net` | `create-pr` | `acme/frontend` (written by ticket author) |
170
+ | `Jira issue-to-PR - backend` | `https://acme.atlassian.net` | `create-pr-backend` | `acme/backend` (written by ticket author) |