## TL;DR
Concurrency groups fail to cancel for two reasons: the group expression evaluates differently per run (so the runs are not actually in the same group), or cancel-in-progress is not set (the default is to queue, not cancel). Check the evaluated group name in the run logs first; identical runs must show identical group names.

## The query
```text
GitHub Actions concurrency group not canceling old runs: how to debug
```

## Use this when
- Old runs continue after new pushes
- Deploys race between concurrent runs
- Concurrency seems configured but ineffective
- After changing group expressions

## Not for when
- Parallel jobs within one run (different mechanism)
- Runner capacity queuing
- Step-level timeouts

## Steps

### Step 1: Verify the group names match across runs
Check the evaluated concurrency group in each run's logs. If run A is in group "deploy-main" and run B is in "deploy-main-2", they are different groups and will never cancel each other. The expression must be deterministic per branch.
Expected output: group names compared; mismatch found or ruled out.

### Step 2: Confirm cancel-in-progress is set
The default concurrency behavior queues; cancellation requires cancel-in-progress: true explicitly. A group without it serializes runs instead of canceling, which looks like "not canceling" but is working as configured.
Expected output: the setting confirmed present, or added.

### Step 3: Check where the concurrency is defined
Concurrency at the workflow top level groups whole runs; at the job level it groups jobs. A job-level group does not cancel other jobs' runs. For deploy cancellation you almost always want the top-level group.
Expected output: the concurrency scope matching the intent (whole run vs job).

### Step 4: Watch for expression pitfalls
Dynamic group names using github.sha change every run (never grouping); using run_id is similarly useless for cancellation. Group by branch or PR number: the stable identity of the thing you want serialized.
Expected output: the group expression using a stable identifier.

### Step 5: Test with two rapid pushes
Push twice in quick succession and watch: the first run should cancel when the second starts. This is the definitive test; configuration review alone misses evaluation subtleties.
Expected output: cancellation observed working end to end.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_7q_zzNQQ68Cnsyy8OMTNYg
