GitHub Action reference
crypticsaiyan/twin@main verifies pull requests that claim to fix an issue, in the reporter’s environment. The walkthrough is in Check fix PRs in CI. The source is action.yml and scripts/action/ (run.sh, find-capsule.sh, comment.sh), tested in test/action/action.test.ts.
It is a composite action with three steps:
| Step | What runs |
|---|---|
actions/setup-node@v4 |
Node 22. |
| Install twin | npx --yes pnpm@10.20.0 install --frozen-lockfile in the action’s directory (github.action_path); this also builds twin. |
| Verify in the reporter’s environment | scripts/action/run.sh, with the inputs and the PR’s number, head SHA and head repository clone URL in its environment. |
run.sh then:
-
Fails with
twin verify checks pull requests; run it on pull_request events.when there is no pull request in the event. -
When
SOLARI_API_KEYis empty (as on pull requests from forks), prints the noticeNo Solari API key (secrets are not passed to pull requests from forks); nothing to verify., setsverdict=skippedand exits successfully, before looking for a capsule. -
Uses the
capsuleinput if set. Otherwisefind-capsule.shlists the PR’s closing issue references (gh pr view --json closingIssuesReferences) and, for each issue, searches the body and then the comments for the first link matchinghttps://github\.com/user-attachments/files/[0-9]+/[^][()<>" ]+\.json|https://[^][()<>" ]*capsule[^][()<>" ]*\.jsonthat is, a GitHub issue attachment ending in
.json, or any https link to a.jsonfile withcapsulein its URL. -
With no capsule: prints the notice
No twin capsule is attached to an issue this pull request closes; nothing to verify., setsverdict=skippedand exits successfully. -
Runs
twin verify "$capsule" --ref "$HEAD_SHA" --repo "$HEAD_REPO" --attempts "$ATTEMPTS" --comment <file>. -
If no comment file was written, fails with
twin verify stopped before reaching a verdict (exit <code>). -
Reads the verdict from the comment’s first line (
<!-- twin-verify verdict=… -->), sets theverdictoutput, and appends the comment to the job summary ($GITHUB_STEP_SUMMARY). -
If
commentistrue,comment.shedits the last PR comment starting with<!-- twin-verify, or posts a new one if there is none. -
Fails with
twin: <verdict> in the reporter's environment.unless the verdict isfixed.
Inputs
Section titled “Inputs”| Input | Required | Default | Description |
|---|---|---|---|
solari-api-key |
yes | Solari API key. Store it as a repository secret and pass ${{ secrets.SOLARI_API_KEY }}. |
|
capsule |
no | '' |
Capsule path or https URL. By default, the capsule attached to an issue the pull request closes. A path resolves against the job’s working directory, so check the repository out first for a committed capsule. |
attempts |
no | 3 |
Runs of the failing command (twin verify --attempts). |
comment |
no | true |
Post the verdict as a pull request comment, updated on each push. Any other value skips the comment; the job summary still gets it. |
github-token |
no | ${{ github.token }} |
Token for reading issues and writing the comment (used by gh). |
Outputs
Section titled “Outputs”| Output | Values |
|---|---|
verdict |
fixed, still-failing, different-failure, flaky, inconclusive, or skipped (no Solari key, or no capsule found). Not set when the job fails before a verdict. |
| Verdict | Job result |
|---|---|
fixed |
success |
still-failing, different-failure, flaky, inconclusive |
failure |
skipped (no key, or no capsule) |
success, with a notice |
Job summary and comment
Section titled “Job summary and comment”Every run that reaches a verdict appends the comment’s Markdown to the job summary. The comment has the hidden marker line, a headline (✅ FIXED in the reporter's environment, ❌ STILL FAILING in the reporter's environment, ❌ DIFFERENT FAILURE in the reporter's environment, ⚠️ FLAKY: attempts disagreed or ⚠️ INCONCLUSIVE: setup failed or an attempt timed out, or ⚠️ INCONCLUSIVE: the failure did not reproduce without the fix), a one-line explanation, a table (command, reporter’s environment, checked commit, attempts passed, capsule link) and the verify log in a collapsed <details> section.
Permissions
Section titled “Permissions”permissions: contents: read issues: read pull-requests: write| Permission | Why |
|---|---|
contents: read |
Read the pull request and its closing issue references. |
issues: read |
Read the linked issues and their comments to find the capsule. |
pull-requests: write |
Post and update the verdict comment. |
Minimal workflow
Section titled “Minimal workflow”name: twinon: pull_requestpermissions: contents: read issues: read pull-requests: writejobs: verify: runs-on: ubuntu-latest steps: - uses: crypticsaiyan/twin@main with: solari-api-key: ${{ secrets.SOLARI_API_KEY }}Requirements and security model
Section titled “Requirements and security model”- Public repository. The sandbox clones the PR’s head repository and commit without credentials.
- The PR’s code runs only inside the Solari sandbox. The runner installs and runs twin itself; it never checks out or executes the pull request’s code.
- The key stays on the runner. It is passed to twin as
SOLARI_API_KEYand never copied into the sandbox. - Fork PRs get no secrets under the plain
pull_requesttrigger, so the check works for same-repository branches, which is how coding agents usually open PRs. On a fork PR the key is empty, so the action skips with a notice andverdict=skippedinstead of failing. - The runner needs
bashand the GitHub CLI (gh), both present on GitHub-hosted Ubuntu runners.