feat(actions): add force-cancel workflow run API (#38756)

Add `POST /repos/{owner}/{repo}/actions/runs/{run}/force-cancel`, the
counterpart of [GitHub's force-cancel endpoint](https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2026-03-10#force-cancel-a-workflow-run).

It cancels a run like `POST .../cancel`, but bypasses the graceful
cancelling handshake with the runner and stops running tasks
immediately.

Permissions and responses match the `/cancel` endpoint.

References:

- https://github.blog/changelog/2023-09-21-github-actions-force-cancel-workflows/
- https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2026-03-10#force-cancel-a-workflow-run
- https://github.com/orgs/community/discussions/123240
This commit is contained in:
Zettat123
2026-08-07 08:38:14 -06:00
committed by GitHub
parent 2657756cac
commit 9fc5d20006
10 changed files with 396 additions and 19 deletions
+14 -2
View File
@@ -12,10 +12,21 @@ import (
)
// CancelRun cancels a run's cancellable jobs and returns the run's post-cancellation state.
// A runner that supports it gets to run its post-cancel cleanup before the job reaches its final status.
func CancelRun(ctx context.Context, run *actions_model.ActionRun, jobs []*actions_model.ActionRunJob) (*actions_model.ActionRun, error) {
return cancelRun(ctx, run, jobs, false)
}
// ForceCancelRun cancels a run like CancelRun, but does not wait for the runners to acknowledge it:
// the jobs are marked cancelled at once and whatever a runner reports for them afterwards is discarded.
func ForceCancelRun(ctx context.Context, run *actions_model.ActionRun, jobs []*actions_model.ActionRunJob) (*actions_model.ActionRun, error) {
return cancelRun(ctx, run, jobs, true)
}
func cancelRun(ctx context.Context, run *actions_model.ActionRun, jobs []*actions_model.ActionRunJob, force bool) (*actions_model.ActionRun, error) {
var updatedJobs []*actions_model.ActionRunJob
if err := db.WithTx(ctx, func(ctx context.Context) (err error) {
updatedJobs, err = actions_model.CancelJobs(ctx, jobs)
updatedJobs, err = actions_model.CancelJobs(ctx, jobs, force)
if err != nil {
return fmt.Errorf("CancelJobs: %w", err)
}
@@ -27,7 +38,8 @@ func CancelRun(ctx context.Context, run *actions_model.ActionRun, jobs []*action
return nil, err
}
CreateCommitStatusForRunJobs(ctx, run, jobs...)
// updatedJobs, not jobs: cancelOneJob re-reads the cancelled rows, the input ones still carry their pre-cancel status
CreateCommitStatusForRunJobs(ctx, run, updatedJobs...)
EmitJobsIfReadyByJobs(updatedJobs)
NotifyWorkflowJobsStatusUpdate(ctx, updatedJobs...)
+1 -1
View File
@@ -182,7 +182,7 @@ func CancelAbandonedJobs(ctx context.Context) error {
return err
}
updatedJobs, err := actions_model.CancelJobs(ctx, abandonedJobs)
updatedJobs, err := actions_model.CancelJobs(ctx, abandonedJobs, false)
if err != nil {
log.Warn("cancel abandoned jobs: %v", err)
}