mxschmitt/action-tmate: an SSH session inside a GitHub Actions runner
Debug your GitHub Actions via SSH by using tmate to get access to the runner system itself.
At a glance
- What is it?
- The action opens a tmate session on the runner so you can inspect the machine a failing job actually ran on. It is a debugging tool, not a CI feature, and its security defaults depend on how you configure it.
- Who is it for?
- Adopt it when a failing step needs interactive inspection of the runner, and set limit-access-to-actor: true plus a timeout-minutes value on the debug step before you do. Do not adopt it as a general-purpose remote shell into your infrastructure, and do not leave it in a workflow that runs on every push: the README's manual workflow_dispatch pattern exists precisely because a debug step left in place blocks the job until someone connects or the timeout fires.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What action-tmate solves that logs cannot
A GitHub Actions job runs on a machine you never see. When a step fails, you get the log lines that step produced and the exit code, and then the runner is discarded. If the failure depends on something the log does not print (an environment variable set by an earlier step, a file left behind by a cache restore, a tool version that differs from your laptop), the log is not enough to find it.
mxschmitt/action-tmate addresses exactly that gap. The README describes it as a way to "interact with the host system on which the actual scripts (Actions) will run", and the two supported paths are SSH and a web shell. The intended user is whoever owns the workflow and is tired of adding print statements to a YAML file, pushing a commit, and waiting for the run to reach the point of failure again. It is not a deployment tool, a remote administration channel, or a replacement for a proper staging environment. It is a breakpoint.
How the tmate session gets onto the runner
The action is a JavaScript action. According to package.json, the entry point is lib/main.js and the build script bundles src/main.js with ncc into lib, which is the compiled artifact the workflow actually executes. The runtime dependencies listed there are @actions/core, @actions/github, @actions/tool-cache and @octokit/rest, so the action talks to the GitHub API and to the tool cache in addition to whatever it does locally.
The repository layout explains the two entry points. There is an action.yml at the top level and a detached/ directory; the update-detached-action.yml script in package.json rewrites action.yml into detached/action.yml, changing the lib/ paths to ../lib/ and flipping the detached default from false to true. That is why the README can say the detached mode "is also available as mxschmitt/action-tmate/detached for convenience": it is a generated copy of the same action with one default changed, not a separate implementation.
At run time the action installs tmate on the runner and starts a session, then prints connection details. In the default mode the step blocks until the session ends. In detached mode it prints the details and lets the job continue, and the README states that the post-job step waits up to 10 minutes for a user to connect before terminating the session and quitting. The detached variant also sets outputs: ssh-command, ssh-address and web-url, which later steps or jobs can consume through the standard outputs mechanism.
Installing action-tmate and getting a first session
There is nothing to install locally. The action is consumed as a workflow step, and the README's minimal example is a complete workflow. Adding this to .github/workflows/ci.yml creates a tmate session on an ubuntu-latest runner after the checkout step:
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3After the run starts, the README says to open the Checks tab in the pull request and scroll to the bottom, where the connection string appears. From there you connect over SSH or through the web terminal. When you exit the shell, the step finishes and the rest of the workflow continues. Supported runners are Linux, macOS and Windows.
The more useful pattern for a workflow you do not want to edit repeatedly is the manual trigger. The README adds a workflow_dispatch input and gates the debug step on it:
on:
workflow_dispatch:
inputs:
debug_enabled:
type: boolean
description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
required: false
default: 'false'and then:
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}You then run the workflow manually on the branch you care about with debug_enabled set to true. The debug step stays in the file but does nothing on ordinary pushes. If you would rather have the session only after a failure, the README's alternative is to condition the step on `if: ${{ failure() }}`, since a failed step otherwise causes all following steps to be skipped.
Access control, sudo and the timeout you must set yourself
The security default deserves attention. The README states that if you have registered public SSH keys with your GitHub profile, tmate is started so that only those keys can connect; otherwise anybody can connect to the session. That is a conditional default, and it depends on the person who triggered the workflow having keys on their profile. To require a key regardless, the README documents the setting limit-access-to-actor: true. Anyone running this action on a public repository should set that, because the alternative is a shell on a runner that can read whatever secrets the job has already exposed to its environment.
The second operational constraint is duration. The README says the tmate session remains open until the workflow times out unless you set your own limit, and shows timeout-minutes: 15 on the step. This is the parameter that controls GitHub Actions usage, and leaving it unset is how a forgotten debug step turns into a long-running job. The README frames the timeout as a way to reduce usage rather than as a safety feature, but in practice it is both.
The third is sudo. Installation commands run through sudo on Linux by default, and the README documents the sudo: false parameter for the case where you get `sudo: not found`. That error is a signal about the runner image rather than about the action, but it is the documented failure mode, so it is worth knowing before you file anything.
Where action-tmate is the wrong tool
The action is a debugging instrument and it behaves like one. It cannot tell you why a step failed unless you are present to look, and in the default mode it holds the job open until someone connects and exits. That makes it unsuitable for unattended pipelines, scheduled jobs and anything where a human is not watching the Checks tab. If your team's problem is flaky tests that fail at 3am, an SSH session does not help; the detached mode with its ssh-command output is closer, but the README's own example for that output is a placeholder that sends a Slack message telling someone they can connect, which still assumes a person on the other end.
There is also a real cost dimension the README acknowledges indirectly. The session runs on a GitHub-hosted runner, so the minutes it consumes are billed like any other job minutes, and a session left open until the workflow timeout is the expensive case. The README's timeout advice is the mitigation, and it is the reader's job to apply it.
Finally, the action does not change what the runner can see. If a secret was never injected into the job, the shell will not find it. Debugging with action-tmate inspects the environment you already built, not the one you meant to build.
How it differs from self-hosted runners and other debug actions
The closest alternative in practice is a self-hosted runner, where you already have a shell on the machine and can attach whenever you like. The difference in approach is where the state lives. A self-hosted runner keeps its filesystem between jobs, which is convenient for inspection and dangerous for reproducibility: the machine drifts, and a failure you cannot reproduce on a fresh runner may be caused by the very state that made debugging easy. action-tmate runs on the ephemeral GitHub-hosted runner, so what you inspect is the environment the job actually had, and it disappears when the job ends. That is the trade: no persistent access, but no drift either.
Among actions, the meaningful distinction is between inspecting the runner and inspecting the logs. Log-based debug actions emit extra output from the steps you already have. action-tmate gives you an interactive shell instead, which means you can run the failing command again, check file permissions, and read files that no step ever printed. The cost is that the job blocks, the session needs a person, and the access control question above becomes yours to answer. For a one-off investigation on a branch, the interactive shell is usually faster than adding more logging and waiting for another run.
Maintenance, versioning and the MIT licence
The repository is not archived, and the last push was on 2026-09-13. The most recent release listed is v3.24 from 2026-05-29, with v3.23 before it in 2025-10-23 and the original v3 in 2020. The v3 tag is the one the README uses in every example, and the release history shows it has kept receiving point releases rather than being frozen, so pinning to v3 gets you fixes without a major-version migration. Pinning to a full tag or commit is the stricter option if you want no movement at all.
The upgrade surface is small. The action is distributed as compiled JavaScript in lib/, so a version bump is a workflow edit and nothing else; there is no binary to install on your side. What can break across versions is the input names and the detached outputs, which is why the README documents them explicitly. The package.json version field is 0.0.0 and the package is marked private, so the npm package is not the distribution channel: the GitHub Action tag is.
The licence is MIT. That permits commercial and private use and modification, and it comes with no warranty, which matters more than usual for a tool that hands out shells. The licence text is in the repository's LICENSE file, and nothing here should be read as legal advice about your own compliance obligations.
Editorial conclusion
Adopt it when a failing step needs interactive inspection of the runner, and set limit-access-to-actor: true plus a timeout-minutes value on the debug step before you do. Do not adopt it as a general-purpose remote shell into your infrastructure, and do not leave it in a workflow that runs on every push: the README's manual workflow_dispatch pattern exists precisely because a debug step left in place blocks the job until someone connects or the timeout fires. Before relying on it, verify which of your jobs run on Linux, macOS or Windows, and check whether the runner image has sudo, since the action runs installation commands through sudo by default and the sudo: false parameter is the documented way out.
Frequently asked questions
What is GitHub Actions and how does it work?
According to the README, GitHub Actions is the workflow system this action plugs into: a job declares runs-on, and each step either runs a command or uses an action. mxschmitt/action-tmate is one such action, added as a step with uses: mxschmitt/action-tmate@v3, and it starts a tmate session on the runner that job is executing on.
Why use GitHub Actions instead of Jenkins?
The README does not compare the two. What it does document is the debugging angle: with this action a failed step can be followed by an interactive SSH or web shell on the runner, and the workflow continues afterwards, which is the specific workflow it was built for.
What are the three types of GitHub Actions?
The README does not categorise actions. The repository itself shows the shape of one: package.json declares main as lib/main.js and a build script that bundles src/main.js with ncc into lib, with action.yml at the top level and a generated copy under detached/.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mxschmitt-action-tmate)