@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.
- package/.release-please-manifest.json +1 -1
- package/README.md +3 -2
- package/automations/catalog/jira-issue-to-pr.json +14 -0
- package/automations/index.js +2 -0
- package/integrations/catalog/atlassian.json +78 -3
- package/integrations/catalog/datadog.json +33 -2
- package/integrations/catalog/superhuman-mail.json +36 -0
- package/integrations/catalog-index.js +45 -43
- package/marketplaces/openhands-extensions.json +10 -0
- package/package.json +1 -1
- package/plugins/cobol-modernization/skills/build-setup/SKILL.md +1 -0
- package/plugins/cobol-modernization/skills/cobol-modernization/SKILL.md +9 -0
- package/plugins/migration-scoring/skills/migration-scoring/SKILL.md +11 -0
- package/plugins/onboarding/skills/agent-readiness-report/SKILL.md +2 -0
- package/plugins/openhands/SKILL.md +7 -0
- package/plugins/release-notes/SKILL.md +9 -0
- package/pyproject.toml +1 -1
- package/python/openhands_extensions/_version.py +1 -1
- package/scripts/build-skills-catalog.mjs +1 -1
- package/skills/add-skill/SKILL.md +2 -0
- package/skills/azure-devops/SKILL.md +12 -1
- package/skills/bitbucket-cloud/SKILL.md +11 -0
- package/skills/code-review/SKILL.md +1 -0
- package/skills/datadog/SKILL.md +3 -1
- package/skills/datadog/references/windows.md +45 -0
- package/skills/discord/SKILL.md +2 -0
- package/skills/docker/SKILL.md +5 -1
- package/skills/github/SKILL.md +3 -1
- package/skills/github/references/windows.md +48 -0
- package/skills/github-pr-review/SKILL.md +1 -0
- package/skills/github-pr-review/references/windows.md +47 -0
- package/skills/github-pr-reviewer/SKILL.md +1 -0
- package/skills/github-pr-reviewer/references/windows.md +61 -0
- package/skills/github-repo-monitor/SKILL.md +1 -0
- package/skills/github-repo-monitor/references/windows.md +61 -0
- package/skills/gitlab/SKILL.md +3 -1
- package/skills/incident-retrospective/SKILL.md +2 -0
- package/skills/index.js +36 -30
- package/skills/iterate/SKILL.md +1 -0
- package/skills/iterate/references/windows.md +41 -0
- package/skills/jira-issue-to-pr/.plugin/plugin.json +19 -0
- package/skills/jira-issue-to-pr/README.md +56 -0
- package/skills/jira-issue-to-pr/SKILL.md +216 -0
- package/skills/jira-issue-to-pr/references/setup.md +170 -0
- package/skills/jira-issue-to-pr/scripts/main.py +341 -0
- package/skills/jupyter/SKILL.md +7 -1
- package/skills/kubernetes/SKILL.md +2 -1
- package/skills/kubernetes/references/windows.md +38 -0
- package/skills/linear/SKILL.md +2 -0
- package/skills/linear/references/windows.md +29 -0
- package/skills/linear-triage/SKILL.md +2 -0
- package/skills/notion/SKILL.md +2 -0
- package/skills/notion/references/windows.md +43 -0
- package/skills/openhands-api/SKILL.md +1 -0
- package/skills/openhands-api/references/windows.md +44 -0
- package/skills/openhands-automation/SKILL.md +1 -0
- package/skills/openhands-automation/references/custom-automation.md +20 -0
- package/skills/openhands-automation/references/windows.md +57 -0
- package/skills/pdflatex/SKILL.md +3 -1
- package/skills/research-brief/SKILL.md +2 -0
- package/skills/skill-creator/SKILL.md +1 -0
- package/skills/skill-creator/references/windows.md +30 -0
- package/skills/slack-channel-monitor/SKILL.md +1 -0
- package/skills/slack-channel-monitor/references/windows.md +64 -0
- package/skills/slack-standup-digest/SKILL.md +2 -0
- package/skills/spark-version-upgrade/SKILL.md +2 -1
- package/skills/ssh/SKILL.md +2 -1
- package/skills/ssh/references/windows.md +59 -0
- package/skills/swift-linux/SKILL.md +2 -1
- package/skills/vercel/SKILL.md +5 -0
- package/tests/test_catalogs.py +6 -1
package/skills/iterate/SKILL.md
CHANGED
|
@@ -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: <conversation URL>".
|
|
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) |
|