Skip to content

ci: skip stale release runs instead of failing - #256

Open
ioannisj wants to merge 2 commits into
mainfrom
ci/skip-stale-release-run
Open

ioannisj wants to merge 2 commits into
mainfrom
ci/skip-stale-release-run

Conversation

@ioannisj

@ioannisj ioannisj commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

On 2026-10-05 GitHub sent a duplicate push webhook for one merge commit on posthog-kmp: two push events for the same SHA, 8 seconds apart. posthog-kmp's release workflow is a port of this repo's release.yml, so we're exposed to the same thing here.

Both events started a Release run. The release concurrency group (cancel-in-progress: false) queued the second behind the first. The first run published the release and pushed the version-bump commit to main. The second one then started, checked out its trigger SHA, still saw the change intent, rebuilt the same candidate and failed at "Check tag and release do not already exist" with "GitHub release already exists". So a red run and a failure event to PostHog for something that wasn't a problem.

The pinned-SHA design (checkout github.sha, patch sha256, "main moved" check before publish) is deliberate and stays. This PR just catches the stale run earlier, in check-changesets, and skips instead of failing: it compares the tip of main with the trigger SHA before looking at change intents, and sets has-changesets=false when they differ.

I think this is safe because the release concurrency group serializes runs. A duplicate or superseded run only starts once the earlier one has finished, so by then either:

  • main has moved past the trigger SHA (the earlier run committed the bump, nothing left to do), or
  • main is unchanged (the earlier run failed or was rejected before committing, and re-running is the retry we want anyway)

It's the same condition the publish job already enforces, just earlier and as a skip. contents: read is enough for gh api .../git/ref/heads/main, so no new permissions.

A sibling PR applies the same change to posthog-kmp.

💚 How did you test it?

  • actionlint .github/workflows/release.yml: the only report is a pre-existing SC2129 style note in prepare-release-candidate, which this PR doesn't touch. It reports identically on main.
  • I extracted the "Check for change intents" script and ran bash -n on it, clean.

No live release was exercised.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm change to generate a change intent file

🤖 Agent context

DRI: @ioannisj
Autonomy: Human-driven (agent-assisted)

@ioannisj
ioannisj requested a review from a team as a code owner October 6, 2026 09:13
@ioannisj ioannisj self-assigned this Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-fork_curl Compliance Report

Date: 2026-10-08T16:36:10.442360+00:00
Duration: 112439ms

⚠️ Some Tests Failed

36/47 tests passed, 11 failed


Capture Tests

⚠️ 19/30 tests passed, 11 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 36ms
Format Validation.Event Has Uuid ✅ 529ms
Format Validation.Event Has Lib Properties ✅ 530ms
Format Validation.Distinct Id Is String ✅ 532ms
Format Validation.Token Is Present ✅ 530ms
Format Validation.Custom Properties Preserved ✅ 532ms
Format Validation.Event Has Timestamp ✅ 530ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 533ms
Retry Behavior.Retries On 503 ❌ 5530ms
Retry Behavior.Does Not Retry On 400 ✅ 2536ms
Retry Behavior.Does Not Retry On 401 ✅ 2535ms
Retry Behavior.Respects Retry After Header ❌ 5538ms
Retry Behavior.Implements Backoff ❌ 15539ms
Retry Behavior.Retries On 500 ❌ 5046ms
Retry Behavior.Retries On 502 ❌ 5536ms
Retry Behavior.Retries On 504 ❌ 5536ms
Retry Behavior.Max Retries Respected ❌ 15549ms
Deduplication.Generates Unique Uuids ✅ 543ms
Deduplication.Preserves Uuid On Retry ❌ 5535ms
Deduplication.Preserves Uuid And Timestamp On Retry ❌ 10542ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ❌ 5541ms
Deduplication.No Duplicate Events In Batch ✅ 539ms
Deduplication.Different Events Have Different Uuids ✅ 532ms
Compression.Sends Gzip When Enabled ✅ 533ms
Batch Format.Uses Proper Batch Structure ✅ 531ms
Batch Format.Flush With No Events Sends Nothing ✅ 518ms
Batch Format.Multiple Events Batched Together ✅ 522ms
Error Handling.Does Not Retry On 403 ✅ 2532ms
Error Handling.Does Not Retry On 413 ✅ 2534ms
Error Handling.Retries On 408 ❌ 5534ms

Failures

retry_behavior.retries_on_503

Expected at least 3 requests, got 1

retry_behavior.respects_retry_after_header

Expected at least 2 requests, got 1

retry_behavior.implements_backoff

Expected at least 3 requests, got 1

retry_behavior.retries_on_500

Expected at least 2 requests, got 1

retry_behavior.retries_on_502

Expected at least 2 requests, got 1

retry_behavior.retries_on_504

Expected at least 2 requests, got 1

retry_behavior.max_retries_respected

Expected 4 requests, got 1

deduplication.preserves_uuid_on_retry

Need at least 2 requests to check retry

deduplication.preserves_uuid_and_timestamp_on_retry

Expected at least 3 requests, got 1

deduplication.preserves_uuid_and_timestamp_on_batch_retry

Expected at least 2 requests, got 1

error_handling.retries_on_408

Expected at least 2 requests, got 1

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 525ms
Request Payload.Flags Request Uses V2 Query Param ✅ 522ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 522ms
Request Payload.Flags Request Omits Authorization Header ✅ 521ms
Request Payload.Token In Flags Body Matches Init ✅ 522ms
Request Payload.Groups Round Trip ✅ 522ms
Request Payload.Groups Default To Empty Object ✅ 521ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 523ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 522ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 523ms
Request Lifecycle.No Flags Request On Init Alone ✅ 516ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 516ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 526ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 523ms
Retry Behavior.Retries Flags On 502 ✅ 624ms
Retry Behavior.Retries Flags On 504 ✅ 624ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 531ms

@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Retrigger

[High risk] Changes release workflow to skip stale runs instead of failing.

The PR is not safe to merge until a moved main can no longer silently discard pending change intents.

Reviews (1) · Last reviewed commit: "ci: skip stale release runs instead of f..."

Comment on lines +47 to +50
if [ "$main_sha" != "$TRIGGER_SHA" ]; then
echo "has-changesets=false" >> "$GITHUB_OUTPUT"
echo "main is at $main_sha, not $TRIGGER_SHA; skipping stale run"
exit 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pending release silently skipped

If an unrelated commit moves main while a release run is waiting, this check skips the run even though its change intents are still pending. That commit does not match the workflow’s .changeset/*.md push filter, so it starts no replacement run and the release can be missed without a failure. Only skip when the intents have been consumed; otherwise keep a release path or fail visibly. The documentation’s claim that a newer run owns the release does not hold in this case.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/release.yml
Line: 47-50

Comment:
**Pending release silently skipped**

If an unrelated commit moves `main` while a release run is waiting, this check skips the run even though its change intents are still pending. That commit does not match the workflow’s `.changeset/*.md` push filter, so it starts no replacement run and the release can be missed without a failure. Only skip when the intents have been consumed; otherwise keep a release path or fail visibly. The documentation’s claim that a newer run owns the release does not hold in this case.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-lib_curl Compliance Report

Date: 2026-10-08T16:36:14.432014+00:00
Duration: 118322ms

✅ All Tests Passed!

47/47 tests passed


Capture Tests

✅ 30/30 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 27ms
Format Validation.Event Has Uuid ✅ 521ms
Format Validation.Event Has Lib Properties ✅ 523ms
Format Validation.Distinct Id Is String ✅ 523ms
Format Validation.Token Is Present ✅ 524ms
Format Validation.Custom Properties Preserved ✅ 522ms
Format Validation.Event Has Timestamp ✅ 523ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 524ms
Retry Behavior.Retries On 503 ✅ 5832ms
Retry Behavior.Does Not Retry On 400 ✅ 2527ms
Retry Behavior.Does Not Retry On 401 ✅ 2524ms
Retry Behavior.Respects Retry After Header ✅ 8533ms
Retry Behavior.Implements Backoff ✅ 16243ms
Retry Behavior.Retries On 500 ✅ 5140ms
Retry Behavior.Retries On 502 ✅ 5631ms
Retry Behavior.Retries On 504 ✅ 5634ms
Retry Behavior.Max Retries Respected ✅ 17048ms
Deduplication.Generates Unique Uuids ✅ 231ms
Deduplication.Preserves Uuid On Retry ✅ 5629ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10839ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5637ms
Deduplication.No Duplicate Events In Batch ✅ 528ms
Deduplication.Different Events Have Different Uuids ✅ 525ms
Compression.Sends Gzip When Enabled ✅ 524ms
Batch Format.Uses Proper Batch Structure ✅ 523ms
Batch Format.Flush With No Events Sends Nothing ✅ 519ms
Batch Format.Multiple Events Batched Together ✅ 513ms
Error Handling.Does Not Retry On 403 ✅ 2526ms
Error Handling.Does Not Retry On 413 ✅ 2524ms
Error Handling.Retries On 408 ✅ 5632ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 523ms
Request Payload.Flags Request Uses V2 Query Param ✅ 521ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 520ms
Request Payload.Flags Request Omits Authorization Header ✅ 522ms
Request Payload.Token In Flags Body Matches Init ✅ 520ms
Request Payload.Groups Round Trip ✅ 522ms
Request Payload.Groups Default To Empty Object ✅ 523ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 521ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 521ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 521ms
Request Lifecycle.No Flags Request On Init Alone ✅ 516ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 509ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 526ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 522ms
Retry Behavior.Retries Flags On 502 ✅ 624ms
Retry Behavior.Retries Flags On 504 ✅ 625ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 524ms

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-socket Compliance Report

Date: 2026-10-08T16:36:57.157265+00:00
Duration: 149351ms

⚠️ Some Tests Failed

36/47 tests passed, 11 failed


Capture Tests

⚠️ 19/30 tests passed, 11 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 20ms
Format Validation.Event Has Uuid ✅ 517ms
Format Validation.Event Has Lib Properties ✅ 516ms
Format Validation.Distinct Id Is String ✅ 518ms
Format Validation.Token Is Present ✅ 518ms
Format Validation.Custom Properties Preserved ✅ 518ms
Format Validation.Event Has Timestamp ✅ 517ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 518ms
Retry Behavior.Retries On 503 ❌ 9226ms
Retry Behavior.Does Not Retry On 400 ✅ 2544ms
Retry Behavior.Does Not Retry On 401 ✅ 2516ms
Retry Behavior.Respects Retry After Header ❌ 9228ms
Retry Behavior.Implements Backoff ❌ 19238ms
Retry Behavior.Retries On 500 ❌ 9231ms
Retry Behavior.Retries On 502 ❌ 9229ms
Retry Behavior.Retries On 504 ❌ 9229ms
Retry Behavior.Max Retries Respected ❌ 19240ms
Deduplication.Generates Unique Uuids ✅ 524ms
Deduplication.Preserves Uuid On Retry ❌ 9228ms
Deduplication.Preserves Uuid And Timestamp On Retry ❌ 14235ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ❌ 9231ms
Deduplication.No Duplicate Events In Batch ✅ 523ms
Deduplication.Different Events Have Different Uuids ✅ 518ms
Compression.Sends Gzip When Enabled ✅ 519ms
Batch Format.Uses Proper Batch Structure ✅ 519ms
Batch Format.Flush With No Events Sends Nothing ✅ 515ms
Batch Format.Multiple Events Batched Together ✅ 510ms
Error Handling.Does Not Retry On 403 ✅ 2519ms
Error Handling.Does Not Retry On 413 ✅ 2522ms
Error Handling.Retries On 408 ❌ 5524ms

Failures

retry_behavior.retries_on_503

Expected at least 3 requests, got 1

retry_behavior.respects_retry_after_header

Expected at least 2 requests, got 1

retry_behavior.implements_backoff

Expected at least 3 requests, got 1

retry_behavior.retries_on_500

Expected at least 2 requests, got 1

retry_behavior.retries_on_502

Expected at least 2 requests, got 1

retry_behavior.retries_on_504

Expected at least 2 requests, got 1

retry_behavior.max_retries_respected

Expected 4 requests, got 1

deduplication.preserves_uuid_on_retry

Need at least 2 requests to check retry

deduplication.preserves_uuid_and_timestamp_on_retry

Expected at least 3 requests, got 1

deduplication.preserves_uuid_and_timestamp_on_batch_retry

Expected at least 2 requests, got 1

error_handling.retries_on_408

Expected at least 2 requests, got 1

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 519ms
Request Payload.Flags Request Uses V2 Query Param ✅ 516ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 518ms
Request Payload.Flags Request Omits Authorization Header ✅ 516ms
Request Payload.Token In Flags Body Matches Init ✅ 516ms
Request Payload.Groups Round Trip ✅ 517ms
Request Payload.Groups Default To Empty Object ✅ 517ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 516ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 517ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 517ms
Request Lifecycle.No Flags Request On Init Alone ✅ 513ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 506ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 519ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 518ms
Retry Behavior.Retries Flags On 502 ✅ 619ms
Retry Behavior.Retries Flags On 504 ✅ 619ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 518ms

@dustinbyrne dustinbyrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI-assisted static review at 6189bbc38b869d0ba7b3b8919e6f364f3112311f. Regression scenarios were not executed.

# earlier run already handled (or consumed) these change intents.
main_sha=$(gh api "repos/$REPOSITORY/git/ref/heads/main" --jq '.object.sha')
if [ "$main_sha" != "$TRIGGER_SHA" ]; then
echo "has-changesets=false" >> "$GITHUB_OUTPUT"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important — unresolved release loses failure visibility

If A adds a release intent and a README-only push B advances main before this check, B schedules no replacement release. A now returns success and skips release/failure jobs, hiding the unresolved attempt that previously failed visibly. Consider restricting successful skips to handled/consumed intents and preserving failure visibility otherwise, with stale-pending and consumed-duplicate cases covered separately.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 An AI agent wrote this on behalf of @ioannisj.

Fixed in c25ee69. When main has moved, the check now reads .changeset/*.md at main's tip and only skips if nothing releasable is left there. So a duplicate run whose intents were already consumed still goes green, but a README-only push landing on top of a pending intent fails check-changesets and tells you to dispatch the workflow on main.

A failure in that job had no alert before, so I added the PostHog failure event there too.

Ran it against real history for both cases: main at 05ddb4a (the #250 intent still pending) fails, main at 5451f4e (consumed by the 4.14.1 release) skips.

@dustinbyrne
dustinbyrne requested a review from a team October 6, 2026 17:09
@turnipdabeets
turnipdabeets requested review from a team and turnipdabeets October 7, 2026 00:32
@ioannisj
ioannisj requested a review from dustinbyrne October 8, 2026 17:17

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants