@openhands/extensions 0.3.0 → 0.4.1

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 (40) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/README.md +3 -2
  3. package/automations/catalog/github-pr-reviewer.json +1 -1
  4. package/automations/catalog/github-repo-monitor.json +1 -1
  5. package/marketplaces/openhands-extensions.json +13 -0
  6. package/package.json +1 -1
  7. package/plugins/issue-duplicate-checker/action.yml +2 -2
  8. package/plugins/pr-review/README.md +1 -1
  9. package/plugins/qa-changes/action.yml +4 -2
  10. package/plugins/release-notes/action.yml +3 -2
  11. package/plugins/release-notes/workflows/release-notes.yml +10 -4
  12. package/plugins/vulnerability-remediation/README.md +4 -2
  13. package/plugins/vulnerability-remediation/action.yml +8 -4
  14. package/pyproject.toml +1 -1
  15. package/scripts/sync_extensions.py +2 -1
  16. package/skills/github-actions/.plugin/plugin.json +19 -0
  17. package/skills/github-actions/README.md +190 -0
  18. package/skills/github-actions/SKILL.md +44 -0
  19. package/skills/github-pr-reviewer/SKILL.md +183 -40
  20. package/skills/github-pr-reviewer/references/state-schema.md +98 -0
  21. package/skills/github-pr-reviewer/scripts/main.py +625 -0
  22. package/skills/github-repo-monitor/README.md +1 -1
  23. package/skills/github-repo-monitor/SKILL.md +9 -9
  24. package/skills/github-repo-monitor/references/github-api.md +3 -3
  25. package/skills/github-repo-monitor/scripts/main.py +11 -11
  26. package/skills/index.js +16 -4
  27. package/skills/openhands-sdk/SKILL.md +3 -0
  28. package/skills/slack-channel-monitor/README.md +2 -1
  29. package/skills/slack-channel-monitor/SKILL.md +22 -4
  30. package/tests/test_sync_extensions.py +14 -0
  31. /package/plugins/issue-duplicate-checker/scripts/{post_duplicate_notice.js → post_duplicate_notice.cjs} +0 -0
  32. /package/plugins/issue-duplicate-checker/scripts/{remove_duplicate_candidate_label.js → remove_duplicate_candidate_label.cjs} +0 -0
  33. /package/skills/github-pr-reviewer/commands/{pr-reviewer:setup.md → pr-reviewer-setup.md} +0 -0
  34. /package/skills/github-repo-monitor/commands/{github-monitor:poll.md → github-monitor-poll.md} +0 -0
  35. /package/skills/incident-retrospective/commands/{incident-retro:setup.md → incident-retro-setup.md} +0 -0
  36. /package/skills/linear-triage/commands/{linear-triage:setup.md → linear-triage-setup.md} +0 -0
  37. /package/skills/openhands-automation/commands/{automation:create.md → automation-create.md} +0 -0
  38. /package/skills/research-brief/commands/{research-brief:setup.md → research-brief-setup.md} +0 -0
  39. /package/skills/slack-channel-monitor/commands/{slack-monitor:poll.md → slack-monitor-poll.md} +0 -0
  40. /package/skills/slack-standup-digest/commands/{standup-digest:setup.md → standup-digest-setup.md} +0 -0
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.3.0"
2
+ ".": "0.4.1"
3
3
  }
package/README.md CHANGED
@@ -60,7 +60,7 @@ See [`integrations/README.md`](integrations/README.md), [`automations/README.md`
60
60
  ## Extensions Catalog
61
61
 
62
62
  <!-- BEGIN AUTO-GENERATED CATALOG -->
63
- This repository contains **2 marketplace(s)** with **56 extensions** (46 skills, 10 plugins).
63
+ This repository contains **2 marketplace(s)** with **57 extensions** (47 skills, 10 plugins).
64
64
 
65
65
  ### large-codebase
66
66
 
@@ -79,7 +79,7 @@ OpenHands skills for interacting, improving, and refactoring large codebases
79
79
 
80
80
  Official skills and plugins for OpenHands — the open-source AI software engineer.
81
81
 
82
- **52 extensions** (44 skills, 8 plugins)
82
+ **53 extensions** (45 skills, 8 plugins)
83
83
 
84
84
  | Name | Type | Description | Commands |
85
85
  |------|------|-------------|----------|
@@ -100,6 +100,7 @@ Official skills and plugins for OpenHands — the open-source AI software engine
100
100
  | flarglebargle | skill | A test skill that responds to the magic word 'flarglebargle' with a compliment. Use for testing skill activation and ... | — |
101
101
  | frontend-design | skill | Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks ... | — |
102
102
  | github | skill | Interact with GitHub repositories, pull requests, issues, and workflows using the GITHUB_TOKEN environment variable a... | — |
103
+ | github-actions | skill | Create, debug, and test GitHub Actions workflows and custom actions. Use when building CI/CD pipelines, automating wo... | — |
103
104
  | github-pr-review | skill | Post structured PR reviews to GitHub with inline comments/suggestions in a single API call. | `/github-pr-review` |
104
105
  | github-pr-reviewer | skill | Create an automation that reviews GitHub pull requests when they are opened or updated. Inspects the diff, changed fi... | `/pr-reviewer:setup` |
105
106
  | github-repo-monitor | skill | Create a cron automation that polls a GitHub repository for issue and PR comments containing a configurable trigger p... | `/github-monitor:poll` |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "github-pr-reviewer",
3
- "name": "GitHub PR review copilot",
3
+ "name": "GitHub Code Review Agent",
4
4
  "category": "Code review",
5
5
  "description": "Watch pull requests, inspect the diff, and leave a concise review with risks and suggested follow-ups.",
6
6
  "requiredIntegrationIds": [
@@ -7,5 +7,5 @@
7
7
  "popularityRank": 98,
8
8
  "estimatedSetupMinutes": 5,
9
9
  "prompt": "/github-monitor:poll",
10
- "exampleImplementation": "Trigger: cron, every minute (configurable)\nRequired secret: GITHUB_TOKEN\n\n1. Poll GitHub for new issue and PR comments since the last run.\n2. Match comments containing the trigger phrase (case-insensitive, default: @OpenHands).\n3. Post an acknowledgment comment with a link to the new OpenHands conversation.\n4. Forward follow-up replies in the same thread to the running conversation.\n5. Post the agent's final response back to GitHub when the conversation completes."
10
+ "exampleImplementation": "Trigger: cron, every minute (configurable)\nRequired secret: GITHUB_PERSONAL_ACCESS_TOKEN\n\n1. Poll GitHub for new issue and PR comments since the last run.\n2. Match comments containing the trigger phrase (case-insensitive, default: @OpenHands).\n3. Post an acknowledgment comment with a link to the new OpenHands conversation.\n4. Forward follow-up replies in the same thread to the running conversation.\n5. Post the agent's final response back to GitHub when the conversation completes."
11
11
  }
@@ -238,6 +238,19 @@
238
238
  "workflows"
239
239
  ]
240
240
  },
241
+ {
242
+ "name": "github-actions",
243
+ "source": "./skills/github-actions",
244
+ "description": "Create, debug, and test GitHub Actions workflows and custom actions. Use when building CI/CD pipelines, automating workflows, or troubleshooting GitHub Actions.",
245
+ "category": "integration",
246
+ "keywords": [
247
+ "github-actions",
248
+ "workflows",
249
+ "ci-cd",
250
+ "automation",
251
+ "actions"
252
+ ]
253
+ },
241
254
  {
242
255
  "name": "github-pr-review",
243
256
  "source": "./skills/github-pr-review",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openhands/extensions",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Public OpenHands extension catalogs for skills, plugins, integrations, and automation templates.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -258,7 +258,7 @@ runs:
258
258
  if: inputs.mode == 'issue-check' && steps.parsed_result.outputs.should_comment == 'true'
259
259
  uses: actions/github-script@v9
260
260
  env:
261
- ACTION_SCRIPT: ${{ github.action_path }}/scripts/post_duplicate_notice.js
261
+ ACTION_SCRIPT: ${{ github.action_path }}/scripts/post_duplicate_notice.cjs
262
262
  ISSUE_NUMBER: ${{ inputs.issue-number }}
263
263
  SUMMARY: ${{ steps.parsed_result.outputs.summary }}
264
264
  CANDIDATE_ISSUES_JSON: ${{ steps.parsed_result.outputs.candidate_issues_json }}
@@ -341,7 +341,7 @@ runs:
341
341
  if: inputs.mode == 'remove-label'
342
342
  uses: actions/github-script@v9
343
343
  env:
344
- ACTION_SCRIPT: ${{ github.action_path }}/scripts/remove_duplicate_candidate_label.js
344
+ ACTION_SCRIPT: ${{ github.action_path }}/scripts/remove_duplicate_candidate_label.cjs
345
345
  with:
346
346
  github-token: ${{ inputs.github-token }}
347
347
  script: |
@@ -69,7 +69,7 @@ Add the following secrets in your repository settings (**Settings → Secrets an
69
69
  | `GITHUB_TOKEN` | Auto | Provided automatically by GitHub Actions |
70
70
  | `LMNR_SKILLS_API_KEY` | No | Laminar API key (org-level secret; mapped to `LMNR_PROJECT_API_KEY` env var in workflows) |
71
71
 
72
- **Note**: For repositories that need to post review comments from a bot account, use `ALLHANDS_BOT_GITHUB_PAT` instead of `GITHUB_TOKEN`.
72
+ **Note**: To post review comments from a bot account instead of `GITHUB_TOKEN`, use a bot token scoped to the repository's trust boundary. On **public** repositories, use `OPENHANDS_BOT_GITHUB_PAT_PUBLIC` — a fine-grained token limited to public repositories. On **private** repositories, use a bot token scoped to that repository. Never give a public repository a token that can reach private repositories.
73
73
 
74
74
  ### 3. Customize the Workflow (Optional)
75
75
 
@@ -119,6 +119,8 @@ runs:
119
119
  env:
120
120
  LLM_API_KEY: ${{ inputs.llm-api-key }}
121
121
  GITHUB_TOKEN: ${{ inputs.github-token }}
122
+ GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }}
123
+ INPUTS_LLM_MODEL: ${{ inputs.llm-model }}
122
124
  run: |
123
125
  if [ -z "$LLM_API_KEY" ]; then
124
126
  echo "Error: llm-api-key is required."
@@ -130,9 +132,9 @@ runs:
130
132
  fi
131
133
 
132
134
  echo "PR Number: ${{ github.event.pull_request.number }}"
133
- echo "PR Title: ${{ github.event.pull_request.title }}"
135
+ echo "PR Title: $GITHUB_EVENT_PULL_REQUEST_TITLE"
134
136
  echo "Repository: ${{ github.repository }}"
135
- echo "LLM model: ${{ inputs.llm-model }}"
137
+ echo "LLM model: $INPUTS_LLM_MODEL"
136
138
 
137
139
  - name: Run QA validation
138
140
  if: steps.preflight.outputs.skip != 'true'
@@ -109,9 +109,10 @@ runs:
109
109
  shell: bash
110
110
  env:
111
111
  GITHUB_TOKEN: ${{ inputs.github-token }}
112
+ INPUTS_TAG: ${{ inputs.tag }}
112
113
  run: |
113
114
  if [ -f release_notes.md ]; then
114
- echo "Updating release notes for tag ${{ inputs.tag }}"
115
- gh release edit "${{ inputs.tag }}" --notes-file release_notes.md || \
115
+ echo "Updating release notes for tag $INPUTS_TAG"
116
+ gh release edit "$INPUTS_TAG" --notes-file release_notes.md || \
116
117
  echo "Note: Could not update release. Release may not exist yet."
117
118
  fi
@@ -38,11 +38,14 @@ jobs:
38
38
 
39
39
  - name: Determine tag
40
40
  id: get-tag
41
+ env:
42
+ GITHUB_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }}
43
+ GITHUB_REF_NAME: ${{ github.ref_name }}
41
44
  run: |
42
45
  if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
43
- echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
46
+ echo "tag=$GITHUB_EVENT_INPUTS_TAG" >> $GITHUB_OUTPUT
44
47
  else
45
- echo "tag=${{ github.ref_name }}" >> $GITHUB_OUTPUT
48
+ echo "tag=$GITHUB_REF_NAME" >> $GITHUB_OUTPUT
46
49
  fi
47
50
 
48
51
  - name: Generate Release Notes
@@ -57,10 +60,13 @@ jobs:
57
60
  github-token: ${{ secrets.GITHUB_TOKEN }}
58
61
 
59
62
  - name: Display generated notes
63
+ env:
64
+ TAG: ${{ steps.get-tag.outputs.tag }}
65
+ PREVIOUS_TAG: ${{ steps.release-notes.outputs.previous-tag }}
60
66
  run: |
61
- echo "## Release Notes for ${{ steps.get-tag.outputs.tag }}"
67
+ echo "## Release Notes for $TAG"
62
68
  echo ""
63
- echo "Previous tag: ${{ steps.release-notes.outputs.previous-tag }}"
69
+ echo "Previous tag: $PREVIOUS_TAG"
64
70
  echo "Commits: ${{ steps.release-notes.outputs.commit-count }}"
65
71
  echo "Contributors: ${{ steps.release-notes.outputs.contributor-count }}"
66
72
  echo "New contributors: ${{ steps.release-notes.outputs.new-contributor-count }}"
@@ -90,12 +90,14 @@ on:
90
90
 
91
91
  ### Optional: Use a Bot Account
92
92
 
93
- For better PR attribution, use a bot PAT:
93
+ For better PR attribution, use a bot PAT scoped to the repository's trust boundary. On **public** repositories, use the public-scoped token:
94
94
 
95
95
  ```yaml
96
- github-token: ${{ secrets.ALLHANDS_BOT_GITHUB_PAT || secrets.GITHUB_TOKEN }}
96
+ github-token: ${{ secrets.OPENHANDS_BOT_GITHUB_PAT_PUBLIC || secrets.GITHUB_TOKEN }}
97
97
  ```
98
98
 
99
+ On **private** repositories, use a bot token scoped to that repository instead. Never give a public repository a token that can reach private repositories.
100
+
99
101
  ## Usage
100
102
 
101
103
  ### Automatic Scheduled Scans
@@ -96,6 +96,10 @@ runs:
96
96
  env:
97
97
  LLM_API_KEY: ${{ inputs.llm-api-key }}
98
98
  GITHUB_TOKEN: ${{ inputs.github-token }}
99
+ INPUTS_EXTENSIONS_VERSION: ${{ inputs.extensions-version }}
100
+ INPUTS_LLM_MODEL: ${{ inputs.llm-model }}
101
+ INPUTS_SEVERITY_THRESHOLD: ${{ inputs.severity-threshold }}
102
+ INPUTS_MAX_VULNERABILITIES: ${{ inputs.max-vulnerabilities }}
99
103
  run: |
100
104
  if [ -z "$LLM_API_KEY" ]; then
101
105
  echo "Error: llm-api-key is required."
@@ -108,10 +112,10 @@ runs:
108
112
  fi
109
113
 
110
114
  echo "Repository: ${{ github.repository }}"
111
- echo "Extensions Version: ${{ inputs.extensions-version }}"
112
- echo "LLM Model: ${{ inputs.llm-model }}"
113
- echo "Severity Threshold: ${{ inputs.severity-threshold }}"
114
- echo "Max Vulnerabilities: ${{ inputs.max-vulnerabilities }}"
115
+ echo "Extensions Version: $INPUTS_EXTENSIONS_VERSION"
116
+ echo "LLM Model: $INPUTS_LLM_MODEL"
117
+ echo "Severity Threshold: $INPUTS_SEVERITY_THRESHOLD"
118
+ echo "Max Vulnerabilities: $INPUTS_MAX_VULNERABILITIES"
115
119
 
116
120
  - name: Run vulnerability scan
117
121
  id: scan
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "extensions"
3
- version = "0.3.0"
3
+ version = "0.4.1"
4
4
  description = "OpenHands extensions, plugins, and skills"
5
5
  requires-python = ">=3.12"
6
6
 
@@ -143,7 +143,8 @@ def collect_needed_commands() -> list[CommandSpec]:
143
143
  meta = parse_frontmatter(skill_md.read_text())
144
144
  desc = str(meta.get("description", ""))
145
145
  for trigger in slash_triggers(meta):
146
- cmd_name = trigger.lstrip("/")
146
+ # Replace colons with dashes for cross-platform filename compatibility
147
+ cmd_name = trigger.lstrip("/").replace(":", "-")
147
148
  cmd_path = skill_dir / "commands" / f"{cmd_name}.md"
148
149
  needed.append(CommandSpec(path=cmd_path, trigger=trigger, description=desc))
149
150
  return needed
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "github-actions",
3
+ "version": "1.0.0",
4
+ "description": "Create, debug, and test GitHub Actions workflows and custom actions. Use when building CI/CD pipelines, automating workflows, or troubleshooting GitHub Actions.",
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
+ "github-actions",
14
+ "workflows",
15
+ "ci-cd",
16
+ "automation",
17
+ "actions"
18
+ ]
19
+ }
@@ -0,0 +1,190 @@
1
+ # GitHub Actions Skill
2
+
3
+ A skill focused on **effectiveness** when working with GitHub Actions — monitoring runs, reading logs, and developing actions with confidence rather than guessing.
4
+
5
+ ## Overview
6
+
7
+ This skill provides guidance for:
8
+ - Creating and structuring workflows
9
+ - Building custom composite and reusable actions
10
+ - Testing actions locally (before burning CI minutes) and in CI
11
+ - Debugging failed workflows from real evidence (logs), not assumptions
12
+ - Avoiding common pitfalls around permissions, secrets, and fork PRs
13
+
14
+ ## When to Use This Skill
15
+
16
+ - Creating new GitHub Actions workflows
17
+ - Building custom composite or reusable actions
18
+ - Debugging workflow failures
19
+ - Setting up CI/CD pipelines
20
+ - Troubleshooting permission or secret issues
21
+ - Testing actions before merging to main
22
+
23
+ ## Cost Awareness
24
+
25
+ Every workflow run consumes CI minutes (billed minutes on private repos, shared capacity on public ones).
26
+ Plan before you push:
27
+
28
+ - Prefer one targeted commit that exercises the change over multiple speculative pushes
29
+ - Use `workflow_dispatch` with inputs to manually trigger only what you need
30
+ - Use `paths:` filters and `if:` conditions so jobs only run when relevant
31
+ - Run with `act` locally before opening a PR when feasible
32
+ - Cancel obsolete runs with `concurrency:` groups (don't pay for runs you've already invalidated)
33
+ - Use the smallest matrix that proves the change works; expand only if needed
34
+
35
+ ## Develop With Confidence: The Loop
36
+
37
+ Don't change → push → hope. Use this loop:
38
+
39
+ 1. **Read** the existing workflow / action carefully. Note triggers, permissions, inputs, env, and secrets.
40
+ 2. **Reproduce locally** with `act` when possible (see below).
41
+ 3. **Add visibility** — debug steps that print non-secret inputs, refs, and intermediate state.
42
+ 4. **Push a single focused commit**, then watch the run live.
43
+ 5. **Read the full failed-job log** (not just the summary) before editing.
44
+ 6. **Form a hypothesis from the log**, change the smallest thing that tests it, push, watch again.
45
+
46
+ ## Monitoring Runs With `gh`
47
+
48
+ ```bash
49
+ # Watch the most recent run from the current branch in real time
50
+ gh run watch
51
+
52
+ # Watch a specific run
53
+ gh run watch <run-id>
54
+
55
+ # Auto-refresh PR checks until they finish (every 10s)
56
+ gh pr checks <pr-number> --watch --interval 10
57
+
58
+ # List recent runs for the repo / branch
59
+ gh run list --branch <branch> --limit 10
60
+
61
+ # Get the run ID for the latest run on a branch
62
+ gh run list --branch <branch> --limit 1 --json databaseId --jq '.[0].databaseId'
63
+ ```
64
+
65
+ ## Reading Logs (Don't Guess)
66
+
67
+ ```bash
68
+ # Full log for a run
69
+ gh run view <run-id> --log
70
+
71
+ # Just the failed steps (much shorter)
72
+ gh run view <run-id> --log-failed
73
+
74
+ # Log for a specific job (useful for matrix builds)
75
+ gh run view <run-id> --log --job <job-id>
76
+
77
+ # List jobs for a run to find the job ID
78
+ gh run view <run-id> --json jobs --jq '.jobs[] | {id, name, conclusion}'
79
+
80
+ # Rerun only the failed jobs (avoid paying to rerun green ones)
81
+ gh run rerun <run-id> --failed
82
+ ```
83
+
84
+ When a step fails, look for:
85
+ - The exact command that exited non-zero (above the `##[error]` line)
86
+ - The shell — bash on Linux, `pwsh` on Windows runners — error messages differ
87
+ - Values printed by your debug step; compare against what the workflow *thought* it was passing in
88
+
89
+ ## Adding Visibility to Actions
90
+
91
+ When creating a new action or hitting a tricky bug, add debug steps. Don't leave them in indefinitely — remove or guard them with `if: runner.debug == '1'` once the issue is understood.
92
+
93
+ ```yaml
94
+ steps:
95
+ # Print non-secret inputs and context at the start
96
+ - name: Debug - inputs and context
97
+ if: runner.debug == '1' || inputs.debug == 'true'
98
+ run: |
99
+ echo "Event: ${{ github.event_name }}"
100
+ echo "Ref: ${{ github.ref }}"
101
+ echo "SHA: ${{ github.sha }}"
102
+ echo "Actor: ${{ github.actor }}"
103
+ echo "PWD: $(pwd)"
104
+ echo "Input.my-param: ${{ inputs.my-param }}"
105
+ echo "Files:"
106
+ ls -la
107
+
108
+ # ... your real steps ...
109
+
110
+ # Verify outcome before the job ends
111
+ - name: Debug - verify outputs
112
+ if: always() && (runner.debug == '1' || inputs.debug == 'true')
113
+ run: |
114
+ echo "Generated files:"
115
+ ls -la dist/ || echo "dist/ not found"
116
+ ```
117
+
118
+ To enable `runner.debug == '1'` for a single run, re-run the workflow with **"Enable debug logging"** checked, or set the repo secret `ACTIONS_RUNNER_DEBUG=true`.
119
+
120
+ **Never** echo `${{ secrets.* }}` — GitHub masks them, but encoded forms (base64, hex, JSON-wrapped) can leak through.
121
+
122
+ ## Testing Locally With `act`
123
+
124
+ `act` ([nektos/act](https://github.com/nektos/act)) runs workflows in Docker locally (Docker must be installed and running). Use it to iterate without burning CI minutes.
125
+
126
+ ```bash
127
+ # Run the default push event
128
+ act
129
+
130
+ # Simulate a pull_request event
131
+ act pull_request
132
+
133
+ # Run a specific job
134
+ act -j build
135
+
136
+ # Pass secrets
137
+ act -s GITHUB_TOKEN="$GITHUB_TOKEN"
138
+
139
+ # Use a larger image closer to GitHub's runner
140
+ act -P ubuntu-latest=catthehacker/ubuntu:full-latest
141
+ ```
142
+
143
+ Caveats: `act` does not perfectly emulate GitHub's runners. Things that may differ: pre-installed tools, `GITHUB_TOKEN` permissions, OIDC, the `secrets.GITHUB_TOKEN` value, and macOS/Windows runners. Treat green-on-`act` as a strong signal, not a guarantee.
144
+
145
+ ## Testing Custom Actions Requires Merge to Main
146
+
147
+ A repository-local custom action referenced as `uses: ./.github/actions/my-action` works from any branch, but:
148
+
149
+ - A custom action published as `uses: owner/repo/action@ref` must have the action's files **on `ref`** to be resolvable
150
+ - `@main` or `@v1` must already exist when the workflow that consumes it runs
151
+ - This is why a brand-new action typically must land on `main` first, then be consumed from feature branches that reference `@main` (or a tag)
152
+
153
+ If you need to iterate on an action and its consumer together, point `uses:` at a SHA on your branch, or develop the action via the local `./.github/actions/…` path until it's stable.
154
+
155
+ ## Common Pitfalls
156
+
157
+ 1. **Custom action not found from a PR** — the action's ref doesn't exist yet. Merge to `main` (or push a tag), then consume.
158
+ 2. **`GITHUB_TOKEN` permissions** — defaults can be read-only. Set `permissions:` explicitly at the workflow or job level.
159
+ 3. **`pull_request` vs `pull_request_target`** — `pull_request` runs in the fork's context (no secrets); `pull_request_target` runs with repo secrets against the base — **dangerous** if you check out PR code and execute it. Don't.
160
+ 4. **Secrets in fork PRs** — even non-`*_target` workflows hide secrets from forks. Don't design workflows that require secrets to run on community PRs.
161
+ 5. **`schedule:` triggers only fire from the default branch** — a `schedule:` trigger added in a PR only fires after it lands on the default branch; `workflow_dispatch` similarly won't appear in the Actions tab UI until merged to default (though it can be triggered via API on any branch); `pull_request`-triggered workflows do run on the PR that introduces them.
162
+ 6. **Path filters are OR, not AND** — any matching path triggers; you can't require all paths.
163
+ 7. **Matrix `fail-fast: true`** (default) cancels other matrix legs on first failure — turn off when you need full coverage.
164
+ 8. **Artifacts** — files don't persist between jobs unless uploaded via `actions/upload-artifact` and downloaded in the consuming job.
165
+ 9. **`env:` scope** — env at workflow, job, and step levels all exist. Step-level shadows job-level shadows workflow-level. Surprises follow if you forget.
166
+ 10. **Pin versions** — use `@v4` (or better, a SHA) rather than `@main`. Floating refs break builds when upstream changes.
167
+
168
+ ## Best Practices
169
+
170
+ 1. **Pin action versions** — `@v4` or SHA, not `@main`
171
+ 2. **Least privilege** — set `permissions:` to the minimum needed
172
+ 3. **Debug steps when introducing or debugging** — remove or gate them with `runner.debug` afterward
173
+ 4. **Cache dependencies** — `actions/cache` saves real minutes
174
+ 5. **Concurrency** — cancel obsolete runs:
175
+ ```yaml
176
+ concurrency:
177
+ group: ${{ github.workflow }}-${{ github.ref }}
178
+ cancel-in-progress: true
179
+ ```
180
+ 6. **Timeouts** — set `timeout-minutes:` on jobs that can hang
181
+ 7. **Fail loudly** — `set -euo pipefail` in non-trivial bash steps
182
+ 8. **Document non-obvious triggers/inputs** at the top of the workflow file
183
+
184
+ ## Resources
185
+
186
+ - [GitHub Actions documentation](https://docs.github.com/en/actions)
187
+ - [`act` — run actions locally](https://github.com/nektos/act)
188
+ - [`gh run` CLI reference](https://cli.github.com/manual/gh_run)
189
+ - [github skill](https://github.com/OpenHands/extensions/tree/main/skills/github) — GitHub API, PRs, issues, and repos
190
+ - [github-pr-review skill](https://github.com/OpenHands/extensions/tree/main/skills/github-pr-review) — post structured code reviews via the GitHub API
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: github-actions
3
+ description: Create, debug, and test GitHub Actions workflows and custom actions. Use when building CI/CD pipelines, automating workflows, or troubleshooting GitHub Actions.
4
+ triggers:
5
+ - github actions
6
+ - github workflow
7
+ - actions workflow
8
+ - gh actions
9
+ - .github/workflows
10
+ ---
11
+
12
+ # GitHub Actions Guide
13
+
14
+ ## Critical Rules
15
+
16
+ **Custom Action Deployment:**
17
+ - New custom actions MUST be merged to the main branch before they can be used
18
+ - After the initial merge, they should be tested from feature branches
19
+
20
+ **Debug Steps:**
21
+ Add debug steps that print non-secret parameters when:
22
+ - Creating a new action, OR
23
+ - Troubleshooting a particularly tricky issue
24
+
25
+ (Not required for every workflow - use when needed)
26
+
27
+ ## Effectiveness Principles
28
+
29
+ Actions cost CI minutes. Be deliberate, not iterative:
30
+
31
+ 1. **Monitor, don't poll** - use `gh run watch` / `gh pr checks --watch` to follow runs live
32
+ 2. **Read logs, don't guess** - fetch the failed job's log before changing code
33
+ 3. **Print actual values** - debug steps reveal the real `inputs`/`github` context, not your assumptions
34
+ 4. **Test locally first** - `act` runs workflows on your machine and avoids burning CI minutes
35
+ 5. **Plan the smallest reproduction** - one job, minimal matrix, narrow trigger before scaling up
36
+
37
+ See [README.md](README.md) for the full debugging workflow, `gh` commands, and YAML debug-step examples.
38
+
39
+ ## Key Gotchas
40
+
41
+ 1. **Secrets unavailable in fork PRs** - `pull_request` has no secrets for forks; `pull_request_target` does but **never check out or execute fork PR code inside it** (RCE with write permissions)
42
+ 2. **Pin action versions** - Use `@v4` or SHA, not `@main` (prevents breaking changes)
43
+ 3. **Explicit permissions** - Set `permissions:` block for GITHUB_TOKEN operations
44
+ 4. **Artifacts for job-to-job data** - Files don't persist between jobs without `upload-artifact`/`download-artifact`