Skip to content

fix(train): tell the contributor when the merge train defers a merge the surface already announced - #9840

Merged
JSONbored merged 5 commits into
mainfrom
fix/merge-train-honest-comment
Jul 29, 2026
Merged

fix(train): tell the contributor when the merge train defers a merge the surface already announced#9840
JSONbored merged 5 commits into
mainfrom
fix/merge-train-honest-comment

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

What the maintainer saw

#9837: fully approved, green, panel said it was merging — then nothing. It looked exactly like the lost-merge class we already fixed.

What actually happened (not that bug)

agent.action.merge | denied | merge train: waiting for older mergeable sibling #9833

The merge train working as designed: #9833 overlaps #9837 and is older, so FIFO queues #9837 behind it; #9833's CI was still running. When #9833 completes, maybeEnqueueSiblingRegateForMergedPr wakes the waiter, and #9483 already covers the blocker-closed/aged-out/evicted cases.

The real defect is honesty: the planner's disposition legitimately concludes wouldMerge before the executor's step-8 train check runs, so the published surface announces a merge — and the enforce-mode denial then records itself audit-only. Publicly the PR claims an action that never happened. A contributor reads a broken promise; an operator reads a regression.

The fix

On an enforce-mode train denial, post one comment: queued behind #N (overlapping work, opened first), merges automatically when it clears, no action needed.

Dedup via the audit trail, deliberately not a transient lock. The first implementation used claimTransientLock and the regression test caught why that's wrong: it fails open ({acquired: true}) on any deployment without a transient cache bound, which would repeat the comment on every denial pass — every few minutes, for up to 24 hours. The comment's own audit row is the durable "did we already say this" fact on every deployment, keyed per (waiting PR, blocker) so a new blocker gets its own line and the same blocker never repeats within the train window. The row is recorded only after a successful post, so a failed post retries instead of silently believing the contributor was told.

Tests

Regression: enforce-mode denial posts exactly one comment naming the blocker; a second pass behind the same blocker denies again but does not repeat it; the merge is never called. Full suite: 25,289 passed. dead-source-files, command-redelivery-guards, dispatch-gate-reasons green.

…the surface already announced

Observed live on #9837: the published panel said the PR was
MERGING -- the planner's disposition legitimately concluded wouldMerge before the
executor's train check ran -- and then the enforce-mode train denial recorded
itself AUDIT-ONLY. Publicly the PR claimed an action that never happened, which
reads as the bot silently breaking its word (and, to an operator, as the old
lost-merge bug recurring when it is actually FIFO queueing working as designed:
9837 was waiting on overlapping older sibling 9833, whose CI was still running).

Post one comment naming the blocker and what happens next. Dedup via the AUDIT
TRAIL, not a transient lock: claimTransientLock fails OPEN on any deployment
without a transient cache bound, which would repeat the comment on every denial
pass. The comment's own audit row (recorded only after a successful post, so a
failed post retries next pass) is the durable "did we say this" fact everywhere,
keyed per (waiting PR, blocker) so a NEW blocker gets its own line and the SAME
blocker never repeats within the 24h train window.
@loopover-orb

loopover-orb Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Warning

⏸️ LoopOver review result - manual review recommended

Review updated: 2026-07-29 14:55:10 UTC

2 files · 1 AI reviewer · 2 blockers · CI green · clean

⏸️ Suggested Action - Manual Review

Review summary
This PR adds a one-time contributor-facing comment when an enforce-mode merge-train denial occurs, deduped via an audit-event row (not a transient lock, per the write-up explaining why claimTransientLock fails open). The mechanism matches its own design: post-then-record only on success so a failed post retries, and the dedup key is scoped per (waiting PR, blocker) so a new blocker gets its own line. The included regression test exercises the exact described scenario (two passes behind the same blocker → exactly one comment) and CI is green.

Nits — 5 non-blocking
  • src/services/agent-action-executor.ts: `countRecentAuditEventsForActorAndTarget(...).catch(() => 0)` treats a transient audit-read failure as 'never told', which could cause the comment to be reposted on a read failure right after a previous successful post — the reverse of the fail-closed posture applied to the post-then-record side; consider distinguishing a query error from a genuine zero-count.
  • src/services/agent-action-executor.ts: the dedup window is bounded to `MERGE_TRAIN_MAX_WAIT_MS`, so if a PR stays queued behind the same unresolved blocker longer than that window, the comment will be posted again — worth confirming this repeat-after-window behavior is intentional (a periodic reminder) rather than accidental drift from the 'never repeats within the train window' claim in the description.
  • The new inline comment block in agent-action-executor.ts is quite long relative to the code it documents; could be tightened.
  • Consider making the catch on `countRecentAuditEventsForActorAndTarget` log/capture the error (similar to other `.catch` sites in this file) rather than silently defaulting to 0, so a genuine DB outage is visible instead of manifesting only as duplicate comments.
  • If the periodic-repeat-after-window behavior is intentional, a short comment near `alreadyToldSinceIso` would help future readers distinguish it from a bug.

Concerns raised — review before merging

  • No linked issue detected: No closing reference or linked issue number was found in the PR metadata/body. — If this PR is intended to solve an issue, link it explicitly in the PR body.
  • Maintainer requires a linked issue: This repo's maintainer focus manifest requires every PR to reference a tracked issue. — Link the relevant issue (for example Closes #123) before opening the PR.
📋 Copy for AI agents — paste into your coding agent
Fix the following blocker(s) from this PR review:

1. No linked issue detected: No closing reference or linked issue number was found in the PR metadata/body. — If this PR is intended to solve an issue, link it explicitly in the PR body.

2. Maintainer requires a linked issue: This repo's maintainer focus manifest requires every PR to reference a tracked issue. — Link the relevant issue (for example `Closes #123`) before opening the PR.

Decision drivers

  • ❌ Code review — 2 blockers (1 reviewer)
  • ❌ Gate result — Blocking (Repo-configured hard blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 345 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 345 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is registered but has no active allocation in the current snapshot.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 345 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 4 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Decision record
  • action: hold · clause: missing_linked_issue
  • config: 5f0c4c55992384c84610a9f506c03d73d532beed36e481938120895149a1f5d4 · pack: oss-anti-slop · ci: passed
  • record: cec4c70f267ecb01da9af2a1da8bfd5a7e7c37169ac781558c0a670b300be244 (schema v5, head 4b9ced7)

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@JSONbored JSONbored self-assigned this Jul 29, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.77%. Comparing base (31e0d4e) to head (4b9ced7).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9840      +/-   ##
==========================================
- Coverage   91.65%   90.77%   -0.88%     
==========================================
  Files         916      916              
  Lines      112763   112773      +10     
  Branches    27087    27089       +2     
==========================================
- Hits       103348   102367     -981     
- Misses       8126     9315    +1189     
+ Partials     1289     1091     -198     
Flag Coverage Δ
backend 94.11% <100.00%> (-1.57%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/services/agent-action-executor.ts 96.86% <100.00%> (+0.08%) ⬆️

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 29, 2026
Codecov flagged the two remaining lines: the branch where createIssueComment
REJECTS. That path is the whole reason the audit row is written after the post
rather than before -- a transient GitHub failure must leave no row, so the next
pass retries instead of believing the contributor was told.

Pins all three states: a failed post still denies and records nothing, the next
pass retries, and once it succeeds it stops.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui 4b9ced7 Commit Preview URL

Branch Preview URL
Jul 29 2026, 02:39 PM

Codecov's last uncovered line was the .catch() on the dedup row's own write. It
is deliberately fail-open: if that best-effort write fails, the worst case is one
duplicate comment on a later pass, and the denial plus merge suppression must be
unaffected. Pinned by failing ONLY that event type, so the denial's own audit
write still succeeds and the test proves the branch rather than the mock.
@JSONbored
JSONbored merged commit 215131b into main Jul 29, 2026
9 checks passed
@JSONbored
JSONbored deleted the fix/merge-train-honest-comment branch July 29, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant