skip-duplicate-actions stops a second GitHub Actions run redoing the first one
Save time and cost when using GitHub Actions
At a glance
- What is it?
- skip-duplicate-actions is a GitHub Action that suppresses redundant workflow runs, either because the same content was already checked, because a run with the same content is still in flight, because only documentation paths changed, or because an older run against an outdated commit can be cancelled. It suits teams paying per minute of CI and tired of a push to a feature branch followed by a merge producing the same test run twice, and its path filters trade GitHub's own paths semantics for a backtracking search of commit history.
- Who is it for?
- Teams with long-running workflows that are triggered twice for the same content, and that want the skip decision made from the commit history rather than from the current diff alone, are the people skip-duplicate-actions is for, and the cancellation feature is what makes it more than a duplicate filter.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 87 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Five separate ways to stop a run, and you pick the subset
The feature list is a menu, and the readme is explicit that you can choose any subset of it. There is skipping of duplicate workflow runs after merges, pull requests or similar, skipping of concurrent or parallel runs for things you do not want to run twice, skipping of ignored paths to speed up documentation changes or similar, skipping if paths were not changed for something like directory-specific tests, and cancelling outdated workflow runs after branch pushes. The framing throughout is cost, since all of those features help to save time and costs, especially for long-running workflows. That framing tells you the design target, which is a repository where a push to a feature branch and the subsequent merge produce two runs of the same test suite, and where a documentation-only change should not trigger a full test matrix.
Duplicate detection is content based, not branch based
The first feature is the one that most people want, and its mechanism is worth understanding. If you work with feature branches you might see lots of duplicate workflow runs, for example when a workflow run is performed on a feature branch and then the workflow run is repeated right after merging the feature branch, and the action prevents that. The decision is made on content, not on branch, and the readme is clear that the action does not care whether you use fast-forward merges, rebase merges or squash merges. The exception is stated too: if a merge yields a result that is different from the source branch, then the resulting workflow run will not be skipped, which is commonly the case if you merge outdated branches. That exception is the right behaviour, because a merge that changed the tree is not a duplicate of anything, and a tool that skipped it would silently drop a genuine test run. The readme mentions full traceability twice, in the duplicate skipping and cancellation sections, and it is worth reading what that means in practice. After clean merges you see a message like Skip execution because the exact same files have been successfully checked in, followed by the URL of the previous run. When a run is cancelled, the message is Cancelled with the previous run URL. So the action does not silently skip; it leaves a trail pointing at the run that justifies the decision. That matters for the teams that use this, because a skip that cannot be explained is indistinguishable from a broken pipeline, and the URL turns a skip into something a reviewer can check. The two defaults are also sensible: manual triggers and cron will never be skipped, since a person pressing the button wants the run to happen, and a scheduled job needs to happen on its own schedule.
Concurrent skipping has five policies, and one of them can skip everything
The second feature handles the case where you do not want a workflow running twice at the same time even if it is triggered twice, and the policies are a menu of five. Always skip is for a workflow you never want running twice at once. Only skip same content is useful when a workflow has both a push and a pull_request trigger, or when you push a tag right after pushing a commit, and it is marked deprecated in favour of the next option. Only skip newer runs with the same content uses same_content_newer, and the readme explains the difference in one sentence: same_content_newer ensures that at least one of those workflows will run, while same_content may skip all of them. Only skip outdated runs is aimed at skip checks that are not at the beginning of a job. Never skip disables the concurrent skipping functionality but still lets you use all the other options such as duplicate skipping.
Path filters look backwards through history, which is the whole point
The third and fourth features are where this action differs most from GitHub's own options, and the difference comes down to looking at previous commits rather than the current diff. GitHub's paths-ignore fails to look at previous commits, which means the outcome depends on how often you push changes, and consequently it does not work for required checks, because if you path-ignore a required check then pull requests will block forever without being mergeable. The action instead provides a more flexible paths_ignore feature with an efficient backtracking algorithm, looking for successful checks in your commit history. The same applies to paths, where GitHub's version cannot skip individual steps in a workflow and does not work with required checks you really want to pass, while this one only skips something if it can find a suitable check in your commit history. The readme is blunt about the original approach, saying that instead of stupidly looking at the current commit, paths_ignore will look for successful checks in your commit-history.
Named path groups let one workflow apply different filters per job
The paths_filter input is a YAML string holding named paths_ignore and paths patterns, and it exists for the case where you have multiple jobs in one workflow and want to skip them based on different patterns. The example in the readme shows two named groups, frontend and backend, each with their own ignore and include patterns, and a backtracking setting inside the group that controls how far back the search goes. The comment explains the two values: a boolean or number where the default of true means unlimited, false disables backtracking completely, and a number such as 5 means to stop after having traced back five commits. The presence of a per-group backtracking limit is a good sign, because a repository with many large documentation trees may not want an unbounded history search on every push. The action reports the result through a paths_result output, and the readme points at an example configuration for using paths_filter. The action is added to a workflow like any other, and the configuration is a set of named inputs rather than a config file. The paths_filter input is the one with structure, and it is a YAML string that names groups of path patterns so that different jobs in one workflow can be skipped on different criteria.
frontend:
paths_ignore:
- 'frontend/docs/**'
paths:
- 'frontend/**'
backend:
paths:
- 'backend/**'The two simpler path inputs are JSON arrays of patterns, with paths_ignore taking ignored path patterns and paths taking path patterns that trigger a skip attempt when non-empty. The example values in the readme are an ignored set of `'["**/README.md", "**/docs/**"]'` and a paths set of `'["platform-specific/**"]'`, both defaulting to an empty array. The two flags people reach for first are cancel_others, defaulting to false, and the duplicate skipping switch, which by default never skips manual triggers or cron runs.
Cancelling outdated runs guarantees a complete check set finishes
The fifth feature is the one that goes furthest, because it stops a running job rather than a queued one. Typically workflows should only run for the most recent commit, so when you push changes to a branch the action can be configured to cancel any previous workflow runs that run against outdated commits. The two properties claimed for it are full traceability, through the cancelled message with the previous run URL, and guaranteed execution, since the cancellation algorithm guarantees that a complete check-set will finish no matter what. That guarantee is the important part. A cancellation policy that simply killed everything in the queue could leave a required check that never runs, so the algorithm is written to make sure one complete pass happens. The option itself is a single boolean, cancel_others, defaulting to false, so the behaviour is opt-in.
The action is a TypeScript project packaged with ncc
The repository layout is what a GitHub Action needs and not much else. There is an action.yml at the top level, the TypeScript source under src, a dist directory holding the compiled entry point that GitHub actually runs, a package.json, and the usual configuration files including an eslint configuration and a prettier configuration. The build script is a plain tsc, the packaging step is ncc build, and the combined script runs build, format, lint and package in order, which is the pipeline a release depends on. The dev dependencies describe the implementation rather than the product, with the Octokit retry plugin and the GitHub toolkit packages, micromatch for the path patterns, js-yaml for the paths_filter parsing, and the official eslint plugin for GitHub Actions rules. The distribution is built with pnpm at version 9.15.0, and the package is marked private, which is correct for something published as an action rather than to a registry.
Where skip-duplicate-actions is the wrong choice
The first wrong case is a repository with quick checks, because the action itself costs a workflow run to make its decision, and a matrix that finishes in a minute does not need the history search. The second is a project whose required checks already use GitHub's own paths filters in a way that works, since the whole argument of this action is that paths-ignore fails for required checks, and if your required checks are not path-filtered there is no problem to solve. The third is a team that cannot accept a skip without a trace, because the traceability messages depend on being able to look up a previous run URL, and a fork without the necessary permissions will not produce them. The fourth is a monorepo where the per-job path filtering is needed but the commit history is enormous, since an unbounded backtracking search over a long history on every push is the kind of thing that is cheap on a small repository and expensive on a large one, which is why the backtracking limit exists. The fifth is a pipeline that legitimately needs two runs of the same content, for example a matrix that tests the same tree against two different environments, since the concurrent-run policies here all assume duplicates are waste.
Editorial conclusion
Teams with long-running workflows that are triggered twice for the same content, and that want the skip decision made from the commit history rather than from the current diff alone, are the people skip-duplicate-actions is for, and the cancellation feature is what makes it more than a duplicate filter. It is the wrong choice for a repository whose workflows are fast and cheap, since the action costs a run of its own and the complexity buys little, and it is the wrong choice if your required checks depend on GitHub's own paths-ignore behaviour, since the readme is explicit that path-ignoring a required check blocks pull requests forever without being mergeable. Check three things before you adopt it: that your required status checks are not one of the things you intend to path-ignore, that you understand the concurrent-run policy, since the same_content mode may skip all of a set of runs while same_content_newer guarantees at least one runs, and that manual triggers and cron schedules are never skipped by default, which is usually the behaviour you want. The repository publishes a v5.3.2 tag from 2026-07-19, the last push was on 2026-07-28, and the package.json declares the MIT licence while the licence field on the repository is recorded as a custom one.
Frequently asked questions
What does skip-duplicate-actions do in a GitHub Actions workflow?
It offers five independent features to optimize GitHub Actions: skipping duplicate workflow runs after merges or pull requests, skipping concurrent or parallel runs of the same workflow, skipping ignored paths such as documentation changes, skipping when specific paths were not changed, and cancelling outdated workflow runs after branch pushes. You can choose any subset of them.
How does skip-duplicate-actions decide a run is a duplicate?
On content rather than on branch. The action does not care whether you use fast-forward merges, rebase merges or squash merges, and it looks for successful checks of the same files in your commit history. If a merge yields a result different from the source branch, the run is not skipped, which is the common case when merging outdated branches.
What is the difference between same_content and same_content_newer?
Both skip newer runs of a workflow when the same content is already running, but same_content_newer ensures that at least one of those workflows will run, while same_content may skip all of them. The readme marks the same_content option as deprecated in favour of same_content_newer.
How does skip-duplicate-actions differ from GitHub paths-ignore?
GitHub's paths-ignore fails to look at previous commits, so the outcome depends on how often you push, and it does not work for required checks, because path-ignoring a required check blocks pull requests forever without being mergeable. This action instead looks for successful checks in your commit history using a backtracking algorithm, and provides paths and paths_ignore inputs that only skip something if a suitable check exists in the history.
What is the paths_filter input for in skip-duplicate-actions?
It is a YAML string holding named paths_ignore and paths patterns, useful when one workflow has multiple jobs that should be skipped based on different patterns. Each named group can also carry a backtracking setting, where true is unlimited, false disables backtracking and a number such as 5 stops after tracing back five commits.
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/fkirc-skip-duplicate-actions)Was this useful?