Why a pull request triggers runs on stacks it doesn't touch

Last updated: September 10, 2026

Fix: attach the default push policy with the propose { affected } line removed.

Why it happens

Without a push policy, the default starts a proposed run if either rule matches:

  • affected - checks input.push.affected_files

  • affected_pr - checks input.pull_request.diff

push.affected_files contains every file touched by every commit in the push, not just the head commit. Rebasing on the tracked branch, merging main into the branch, or pushing a batch of commits all expand that list, so stacks that the final PR diff never touched look affected - and re-trigger on each new commit.

pull_request.diff is the base-to-head diff, i.e. the file list you actually see on the PR.

The policy

Removing propose { affected } leaves affected_pr as the only route to a proposed run:

package spacelift

track if {
  affected
  input.push.branch == input.stack.branch
}

# Proposed runs come from the PR diff only, not the full push file list.
propose if affected_pr

ignore if {
  not affected
  not affected_pr
}

ignore if input.push.tag != ""

affected if {
  some filepath in input.push.affected_files
  startswith(normalize_path(filepath), normalize_path(input.stack.project_root))
}

affected if {
  some filepath in input.push.affected_files
  some glob_pattern in input.stack.additional_project_globs
  glob.match(glob_pattern, ["/"], normalize_path(filepath))
}

affected_pr if {
  some filepath in input.pull_request.diff
  startswith(normalize_path(filepath), normalize_path(input.stack.project_root))
}

affected_pr if {
  some filepath in input.pull_request.diff
  some glob_pattern in input.stack.additional_project_globs
  glob.match(glob_pattern, ["/"], normalize_path(filepath))
}

normalize_path(path) := trim(path, "/")

The Rego v0 equivalent is on the default policy docs page. You can use the autoattach label to attach this to every stack.

Confirming the cause

Add sample := true and check the policy samples. They show the real push.affected_files and pull_request.diff per evaluation, which distinguishes an inflated commit range from a project_root or glob mismatch.