fix(review-pr): make review lock release token-independent and TTL crash-safe#49
Open
Sayt-0 wants to merge 1 commit into
Open
fix(review-pr): make review lock release token-independent and TTL crash-safe#49Sayt-0 wants to merge 1 commit into
Sayt-0 wants to merge 1 commit into
Conversation
…ash-safe The concurrent-review lock was broken in both directions: - TTL (600s) was shorter than the agent timeout (2700s), so requests arriving mid-review saw a stale lock and started duplicate reviews. - Release relied on DELETE /actions/caches, which needs actions:write; the review job token lacked it, so the delete failed silently and every request within 600s of a completed review was silently skipped. Release now saves a "-released" marker cache entry that shadows the lock entry on prefix restore (cache saves use the runner cache-service token, independent of github.token scopes). The REST DELETE remains as logged, best-effort cleanup. The TTL becomes a crash-only fallback raised to 3600s, above the agent timeout. The review job now requests actions: write, which callers already grant per the documented setup.
docker-agent
reviewed
Jul 6, 2026
docker-agent
left a comment
Contributor
There was a problem hiding this comment.
Assessment: 🟢 APPROVE
The lock lifecycle logic is sound. The new released-marker mechanism correctly addresses both failure modes described in the PR:
- Fail-open (duplicate reviews): TTL raised to 3600 s — safely above the 2700 s agent timeout plus overhead, ensuring an in-flight review is never treated as stale.
- Fail-closed (silent skips): Release no longer depends on
actions:write— the cache-service token writes the-releasedmarker entry, which the restore step picks up as the newest prefix match on the next run.
Specific points checked:
- Step ID cross-references:
steps.lock-checkandsteps.acquire-lockcorrectly match theid: lock-checkandid: acquire-lockfields added in this diff. Hyphens in step IDs are valid in GitHub Actions expressions. rm -f .cache/review-lock/releasedin acquire: Correctly prevents the newly-saved lock entry from being misread as a release by concurrent runs. Thereleasedfile is only removed from the runner's local disk; the previous release marker still exists in the cache service and remains restorable by other runs.for CACHE_ID in $CACHE_IDSloop: Empty-string expansion in bash produces zero iterations — safe whengh apireturns no results or fails.- Restore-keys prefix match: The
-releasedsuffix on the marker key still starts with the restore prefix (pr-review-lock-<repo>-<pr>-), so the restore step correctly picks it up as the newest entry after a completed review. - DELETE-before-save ordering: A successful DELETE followed by a failed marker save leaves no lock entry in cache, so a concurrent run finds no match and proceeds to acquire — correct behavior.
- Re-run TTL trade-off: The 3600 s TTL means a re-run after a crash must wait up to 60 min (vs. 10 min with the old 600 s TTL). This is an intentional, explicitly documented design decision: the TTL must exceed the 2700 s agent timeout to prevent a long-running review from being treated as stale. The
run_attempt-based save key allows attempt N to save its own lock once it proceeds past the check.
derekmisler
approved these changes
Jul 8, 2026
derekmisler
left a comment
Collaborator
There was a problem hiding this comment.
lock release logic looks solid. the marker-shadows-lock pattern is clean and the acquire-lock gate on release is a nice hardening touch. ci is all green, approving.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes bug B4: the concurrent-review lock was broken in both directions, producing duplicate reviews during long runs and silent skips ("review does not start") after short runs.
Root causes
DELETE /actions/caches, which requiresactions: write; the review job token had noactionsscope, so the delete failed silentlyFix
Release no longer depends on token scopes: a completed run saves a
-releasedmarker cache entry under the same key prefix. Cache saves go through the runner's cache-service token, so they work regardless ofgithub.tokenscopes. The REST DELETE remains as logged, best-effort cleanup. The TTL becomes a crash-only fallback, raised to 3600 s (above the 2700 s agent timeout plus setup/posting overhead, and above the 50 min job cap).Hardening details:
releasedfile, so a run's own lock entry cannot be misread as a release by concurrent runs.steps.acquire-lock.outcome == 'success', so a run that never took the lock cannot release another run's lock.run_attempt, so re-run attempts can save their own entries (cache keys are immutable).Changes
review-pr/action.yml.github/workflows/review-pr.ymlactions: writeon the review job (also fixes the silently failing feedback-artifact list/delete calls).github/workflows/self-review-pr.yml,.github/workflows/test-e2e-reviewer.ymlactions: writesince the called job now requests itAGENTS.md,SECURITY.mdValidation
actionlintpasses (only pre-existing warning about unbuiltdist/)bash -e -o pipefailwith a stubbedghacross 10 scenarios: fresh, stale, released and corrupt lock states, plus release with list-fail and delete-fail tokens; all pass and the marker is always writtentests/*.shpassCompatibility
actions: writeat the call site, as thereview-pr/README.mdand SKILL templates already instruct. Non-compliant callers get an explicit permissions validation error instead of silent lock breakage.