Debug sessions
Pause a failed GitHub Actions job for up to 15 minutes and debug it from a secure terminal in Gitera — no tunnels, no open ports, secrets masked.
Stop pushing test ci 1, test ci 2, test ci 3 to find out why a command fails on the runner. With debug sessions, a failed job pauses and you get a terminal on the runner, inside the Gitera dashboard. Try your fix there, then export it as a patch.
How it works
- Your workflow runs the
gitera-io/gitera-debug-action@v1step after a failure, or on a re-run you armed. - The runner proves who it is with the job's GitHub OIDC token and connects outbound to Gitera over HTTPS.
- Gitera checks your debug policy and plan, then opens a session.
- You open the Debug Runner Session window and type commands. The job waits up to 15 minutes, then continues.
The runner opens no ports and uses no third-party tunnel. No Gitera secret is stored in your repository.
Add the step
permissions:
contents: read
id-token: write # required: lets the runner prove its repo, run and branch
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
- uses: gitera-io/gitera-debug-action@v1
if: ${{ !cancelled() && (failure() || github.run_attempt > 1) }}
with:
mode: ${{ job.status == 'failure' && 'failure' || 'armed' }}
secrets: ${{ toJSON(secrets) }}| Input | Default | What it does |
|---|---|---|
mode | failure | failure opens a session whenever the step runs. armed opens one only when you armed it from Gitera or by commenting /gitera debug on the pull request. See Choose a mode. |
secrets | {} | Pass ${{ toJSON(secrets) }}. Every value is masked in the terminal and in fix diffs, however short, including base64 and URL-encoded forms. Output that could be the start of a secret is held until the next output shows it is not. |
timeout-minutes | 15 | How long the job may stay paused, 1–15 minutes. |
The step always exits successfully, so it never changes the job result. Linux and macOS runners are supported; Windows runners skip the step. The runner needs node, git and python3, which GitHub-hosted runners already have. On self-hosted macOS runners python3 is required; on Linux without it, the step falls back to util-linux script with a fixed terminal size.
Each matrix leg is its own job, so every failing leg can open its own session.
Choose a mode
mode decides when the step may pause the job. A session is armed when someone asked for it ahead of time: with Debug on re-run in Gitera, or with a /gitera debug comment on the pull request. Gitera then re-runs the run, and the armed session waits 60 minutes for a runner to claim it.
| Mode | Step runs, nothing armed | Step runs, a session is armed for this run |
|---|---|---|
failure | Opens a new session and pauses | Takes over the armed session and pauses |
armed | Exits at once; the job continues | Takes over the armed session and pauses |
The snippet above uses both. Its if: runs the step after a failure or on any re-run, and its mode picks failure when the job failed and armed otherwise:
| Case | Step runs? | Mode | Result |
|---|---|---|---|
| A step failed | Yes | failure | Pauses and opens a session |
| First run, everything passed | No | — | Nothing happens |
| Re-run you armed, and the job passes this time | Yes | armed | Pauses, so you can still inspect the runner |
| Someone clicked Re-run without arming | Yes | armed | Exits at once, no pause |
armed exists because a flaky job often passes on the re-run. With failure alone the step would not run, and the armed session would wait for nothing. With armed, a passing re-run still pauses, but only when you asked for it.
failure pauses on every failure, even when nobody joins. Each pause holds the runner for up to timeout-minutes of billed time. To pause only when you ask, set mode: armed and use if: ${{ !cancelled() && github.run_attempt > 1 }}.
Start a session
- Automatically on failure. The job pauses at the step. The run summary and a job annotation link to the session.
- From Gitera. On a workflow page, open Recent runs and click Debug on re-run on a failed run. Gitera arms a session, re-runs the failed jobs, and opens the session in a new tab.
- From a pull request. Write
/gitera debugas a comment on the pull request in GitHub, alone on the first line. See Debug from a pull request comment. - From GitHub. With the Action Link extension, run pages that have a live session show a Debug Runner Session button.
An armed session waits 60 minutes for the re-run to reach the debug step.
Debug from a pull request comment
/gitera debug is not a CLI command. It is a comment you post on the pull request in GitHub, like /rca. The Gitera GitHub App reads it.
/gitera debug/gitera debug 18342095127The command must be alone on the first line of the comment. Text after the first line is ignored.
- Gitera finds the run. Without a run ID, it takes the latest failed run for the PR's latest commit. With a run ID, it uses that run.
- Gitera checks that you may debug it: write access to the repository, or authorship of this pull request.
- Gitera arms a session and re-runs the failed jobs of that run.
- Gitera replies on the PR with a link to the session.
- When the re-run reaches the debug step, the terminal opens. The arm waits 60 minutes.
The workflow must run the debug step on re-runs (github.run_attempt > 1) with mode: armed, like the snippet in Add the step. Without it, the re-run never pauses and the armed session expires.
| Reply on the PR | Why |
|---|---|
| No failed workflow run found for the latest commit | Nothing failed on the PR's latest commit. Pass a run ID, or push and let a run fail first. |
| Gitera could not start a debug session: … | You lack write access and did not open the PR, the branch is protected, the run is still in progress, or a session is already armed for the run. |
| No reply at all | Gitera is not installed on the repository, the organization is on the Free plan, or the comment was edited instead of posted new. |
Debug Sessions page
Monitoring → Debug Sessions (/debug) lists the selected organization's recent sessions. A session belongs to the repository owner; when another of your organizations has a live session, a notice offers Show all organizations. Cards count sessions that are Live, Armed and Refused. Each row shows the repository, run, job, branch, who started it, the trigger and the time left. Open terminal opens a live or armed session in a new tab. Refused rows show the reason. The list refreshes every 10 seconds while a session is live or armed.
Who can join
You must be signed in to Gitera with GitHub and be an active member of the organization (disabled members are refused). On top of that, you need write access to the repository, or you must be the author of the pull request the run belongs to. Everyone else is refused, including read-only members.
An organization can have 5 live sessions at a time.
Protected branches
Sessions are refused on protected branches. By default these are main, master, release/* and release/**, so nested branches such as release/2026/hotfix are refused too.
For pull_request_target runs, Gitera checks the base branch the job runs on, not the PR's head branch.
To change the list, add the Interactive debug policy (block-interactive-debug) on the Policies page and set protectedBranches. Patterns support * (one path segment) and ** (any depth).
The same policy flags workflows where the debug action can run on a protected branch. A step passes only when its triggers cannot reach any protected branch, or when its job and step if: conditions exclude each one with &&-joined terms such as github.ref != 'refs/heads/main' or !startsWith(github.ref, 'refs/heads/release/'). A condition with || does not count as a guard.
Generate Fix Diff
Click Generate Fix Diff in the session window. Gitera shows git diff HEAD from the runner's workspace, plus any new untracked files. Copy it or Download .patch, then apply it locally:
git apply gitera-fix-<run-id>.patchNothing is committed or pushed for you. Diffs over 1 MB are cut and marked as such.
Audit log
Gitera records every session event: arms, runner connects, joins, refused joins, submitted command lines, diff exports and ends. Typed keystrokes are recorded per submitted line, not per key. Credential-shaped values in a submitted line (TOKEN=..., --password ..., bearer tokens, GitHub tokens) are redacted before storage. The audit log is not readable from the dashboard or the API by organization members.
The debug shell does not get the job's OIDC request credentials, so a session user cannot mint new OIDC tokens for the job.
Limits
| Limit | Value |
|---|---|
| Pause per session | 15 minutes |
| Arm window | 60 minutes |
| Live sessions per organization | 5 |
| Fix diff size | 1 MB |
A paused job keeps its runner busy, so it bills runner minutes while you debug.
Troubleshooting
| Symptom | Fix |
|---|---|
| Log says No OIDC token | Add permissions: id-token: write to the workflow or job. |
| Interactive debug sessions are disabled on protected branch | Debug on a feature branch, or change the block-interactive-debug policy. |
| Only repo write collaborators and the PR author can join | Ask a maintainer for write access, or debug from your own PR. |
| Window says Waiting for runner | The re-run has not reached the debug step yet, or the step uses mode: failure and the job passed. |
Flaky tests
Automatic detection of flaky jobs and tests from retries and mixed outcomes on the same commit — with evidence, wasted CI cost, dismissal, retry rules, and alerts.
Cost analytics
Per-run attribution of GitHub Actions minutes and spend — by repository, workflow, runner type, and storage, with wasted-spend and forecasting.