Skip to content

fix(reconciler): use mergedAt, not merged, in gh pr view --json#3651

Merged
mabry1985 merged 1 commit into
mainfrom
fix/reconciler-gh-json-field
May 24, 2026
Merged

fix(reconciler): use mergedAt, not merged, in gh pr view --json#3651
mabry1985 merged 1 commit into
mainfrom
fix/reconciler-gh-json-field

Conversation

@mabry1985
Copy link
Copy Markdown
Contributor

@mabry1985 mabry1985 commented May 24, 2026

Root cause

The Post-Merge reconciler check called `gh pr view --json state,merged` but `merged` is not a valid field on `gh pr view` (the available boolean-ish field is `mergedAt` — null when unmerged, ISO string when merged). Every call exited non-zero with `Unknown JSON field: "merged"`, the catch handler logged at `debug` level (effectively /dev/null), and the feature was silently skipped every tick.

Observed

The 60-second reconciler timer fired 1168 times with 0 failures (because the catch swallows them) and 0 reconciliations across two PRs that had been merged for hours. `WebhookHealthCheck` flagged `"PR has been in review for 1023 min with no CI events"` but nothing tied it back to the reconciler itself being broken.

Manual repro:

```
$ gh pr view 3645 --repo protoLabsAI/protoMaker --json state,merged
Unknown JSON field: "merged"
```

Fix

  • `gh` args: `--json state,mergedAt` instead of `state,merged`
  • `isMerged` predicate: `state === 'MERGED' || mergedAt != null` (belt-and-suspenders, since gh has historically returned both)
  • catch handler: bumped from `debug` to `warn` so this class of bug surfaces immediately if it happens again
  • updated 4 test mocks + the `PRViewResult` interface

Note on #3642

The Phase 2 (branch-fallback) path I shipped in #3642 was correct on its own merits — but Phase 1 silently failing is what made Phase 2 appear necessary in the first place. Both paths now work as intended.

Verification

  • `npm run test:server -- tests/unit/services/post-merge-reconciler-check.test.ts` — 15/15 pass.
  • `npm run typecheck` — server clean.
  • Prettier — clean on touched files.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed pull request merge state detection to properly identify merged pull requests.
  • Improvements

    • Enhanced error logging visibility for GitHub PR polling operations by displaying failures at warning level for better troubleshooting.

Review Change Stack

The Post-Merge reconciler check called `gh pr view --json state,merged`
but `merged` is not a valid field on `gh pr view` (the available
boolean-ish field is `mergedAt` — null when unmerged, ISO string when
merged). Every call exited non-zero with "Unknown JSON field: merged",
the catch handler logged at debug level (effectively /dev/null), and
the feature was silently skipped every tick.

Observed in this session: the 60-second reconciler timer fired 1168
times with 0 failures (because the catch swallows them) and 0
reconciliations across two PRs that had been merged for hours. The
WebhookHealthCheck flagged "PR has been in review for 1023 min with
no CI events" but nothing tied it back to the reconciler being broken.

Fix:
- gh args: `--json state,mergedAt` instead of `state,merged`
- isMerged predicate: `state === 'MERGED' || mergedAt != null`
- catch handler: bumped from `debug` to `warn` so this class of bug
  surfaces immediately if it happens again
- updated tests + interface type

The Phase 2 (branch-fallback) path I shipped in #3642 was correct on
its own merits — but Phase 1 silently failing is what made Phase 2
appear necessary in the first place. Both paths now work as intended.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@mabry1985 mabry1985 enabled auto-merge (squash) May 24, 2026 05:07
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

Updated the GitHub PR polling reconciler to align with the actual gh pr view --json response shape, replacing a nonexistent merged boolean with mergedAt field semantics. Error visibility improved by raising non-fatal PR check logs to warn level. All test mocks updated consistently.

Changes

PR merge detection update

Layer / File(s) Summary
PR view contract and merge detection
apps/server/src/services/maintenance/checks/post-merge-reconciler-check.ts
PRViewResult type updated to use mergedAt: string | null instead of merged: boolean. The gh pr view invocation requests --json state,mergedAt and merge detection now computes isMerged from state === 'MERGED' or mergedAt != null.
Error handling visibility
apps/server/src/services/maintenance/checks/post-merge-reconciler-check.ts
Non-fatal PR check error logging changed from debug to warn level while continuing reconciliation for subsequent features.
Test suite updates
apps/server/tests/unit/services/post-merge-reconciler-check.test.ts
All test mocks across missed-webhook reconciliation, open-PR guards, graceful error handling, backlog/blocked reconciliation, and --repo flag derivation tests updated to use mergedAt: timestamp | null instead of merged: boolean.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A field called merged that never was,
Now mergedAt marks the PR's pause,
Type and logic dance in perfect sync,
Tests confirm each detail's link,
Warnings glow where silence reigned before! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and precisely describes the main fix: replacing the nonexistent merged field with the correct mergedAt field in the gh pr view command.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reconciler-gh-json-field

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: one or more packages not found in the registry.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

Code Review — ? finding(s)

Async review running parallel to CodeRabbit. Findings are advisory; not all are merge blockers.

protoLabs Code Review Report

  • Generated: 2026-05-24T05:08:13Z
  • Git head: c71e1c99d57efb451233569e12c4693af1e9b34e
  • Features mapped: 3
  • Findings: 0

No findings recorded.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/server/tests/unit/services/post-merge-reconciler-check.test.ts (1)

226-230: ⚡ Quick win

Add explicit coverage for OPEN with non-null mergedAt.

The reconciler now treats either signal as merged, but the suite still misses the defensive state: 'OPEN' + mergedAt timestamp case.

✅ Suggested test case
+  it('treats PR as merged when mergedAt is set even if state is OPEN', async () => {
+    const feature = makeFeature();
+    mockFeatureLoaderGetAll.mockResolvedValue([feature]);
+
+    const execFileAsync = makeExecFileAsync({
+      'protoLabsAI/mythxengine#184': {
+        stdout: JSON.stringify({ state: 'OPEN', mergedAt: '2026-05-22T12:00:00Z' }),
+      },
+    });
+
+    const check = new PostMergeReconcilerCheck(makeLoader(), events as any, execFileAsync);
+    const result = await check.run('/home/josh/dev/labs/mythxengine');
+
+    expect(result.checked).toBe(1);
+    expect(result.reconciled).toBe(1);
+    expect(mockFeatureLoaderUpdate).toHaveBeenCalledTimes(1);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/tests/unit/services/post-merge-reconciler-check.test.ts` around
lines 226 - 230, The test suite is missing a case for the reconciler treating
"state: 'OPEN'" with a non-null "mergedAt" as merged; add a new execFileAsync
fixture (using makeExecFileAsync) that returns { state: 'OPEN', mergedAt:
'<timestamp>' } for the same repo key (e.g., 'protoLabsAI/mythxengine#184') and
add assertions in the corresponding test that the reconciler marks that PR as
merged (same expectations as other merged signals); locate the existing
execFileAsync setup in
apps/server/tests/unit/services/post-merge-reconciler-check.test.ts and mirror
its pattern so the reconciler path handling (the code under test) for
OPEN+mergedAt is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/server/tests/unit/services/post-merge-reconciler-check.test.ts`:
- Around line 226-230: The test suite is missing a case for the reconciler
treating "state: 'OPEN'" with a non-null "mergedAt" as merged; add a new
execFileAsync fixture (using makeExecFileAsync) that returns { state: 'OPEN',
mergedAt: '<timestamp>' } for the same repo key (e.g.,
'protoLabsAI/mythxengine#184') and add assertions in the corresponding test that
the reconciler marks that PR as merged (same expectations as other merged
signals); locate the existing execFileAsync setup in
apps/server/tests/unit/services/post-merge-reconciler-check.test.ts and mirror
its pattern so the reconciler path handling (the code under test) for
OPEN+mergedAt is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 708e0961-3e14-4f26-a140-bd341e5bd26a

📥 Commits

Reviewing files that changed from the base of the PR and between 4123cb0 and 6de4b8e.

📒 Files selected for processing (2)
  • apps/server/src/services/maintenance/checks/post-merge-reconciler-check.ts
  • apps/server/tests/unit/services/post-merge-reconciler-check.test.ts

@mabry1985 mabry1985 merged commit 3f0f568 into main May 24, 2026
7 checks passed
@mabry1985 mabry1985 deleted the fix/reconciler-gh-json-field branch May 24, 2026 05:11
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.

1 participant