LearnNewsExamplesServices
Frontmatter
titlefix(github-workflow): needsUpdate compares persisted updatedAt field (#12191)
authorneo-opus-ada
stateMerged
createdAtMay 30, 2026, 8:25 AM
updatedAtMay 30, 2026, 8:33 AM
closedAtMay 30, 2026, 8:33 AM
mergedAtMay 30, 2026, 8:33 AM
branchesdevfix/gh-syncer-efficiency-12191
urlhttps://github.com/neomjs/neo/pull/12195
Merged
neo-opus-ada
neo-opus-ada commented on May 30, 2026, 8:25 AM

Authored by Opus 4.8 (Claude Code). Session de8830c3-8d1b-47f8-90db-bd236c8b9bd2.

Resolves #12191 Related: #12186

IssueSyncer needsUpdate compared oldIssue.updated, but the persisted metadata field is updatedAt (MetadataManager + every writer). So oldIssue.updated was always undefinedneedsUpdate always trueevery issue re-formatted, re-hashed and re-written every sync, defeating the content-hash short-circuit. One-character field fix.

FAIR-band: over-target — sole active implementer (@neo-gpt rate-limited, @neo-gemini benched) + operator-directed lane (epic #12186 P2).

Evidence: L1 (unit: an unchanged archived issue is left on disk untouched — 1 new test, 209 green) → L1 required (no runtime-only ACs). No residuals.

Deltas from ticket (if any)

#12191's second item — hoisting the per-iteration #planBuckets(metadata) out of the reconcile loop (:1010) — was investigated and dropped as unsafe (not deferred):

  • #planBuckets derives each issue's oldVersion from metadata.issues[*].path (:300-306), and the reconcile loop mutates metadata.issues[issueNumber].path per-iteration (:1046, after each archive move). The call is therefore not loop-invariant — per-iteration recomputation reflects in-progress moves; hoisting to a pre-loop snapshot would change chunk-packing behavior.
  • The loop iterates active-closed issues only (:1000-1006 skip already-archived + non-closed), typically few — so the real cost is small, not the ~77M-touch figure the investigation estimated (which assumed iteration over the full archive).

A genuine optimization needs a deeper refactor (stable plan + incremental packing) — out of scope and low-value at this N. The field fix is the substantive bug in #12191.

Test Evidence

  • npm run test-unit -- test/playwright/unit/ai/services/github-workflow/209 passed (1 new, 0 regressions).
  • New test (IssueSyncer.spec): an unchanged archived issue (same updatedAt; sealed-chunk guarantees the path matches, so updatedAt is the sole discriminator) is not re-rendered — its on-disk sentinel survives the sync. Fails pre-fix (always-true needsUpdate overwrites it).
  • node --check; pre-commit whitespace/shorthand hooks green.

Post-Merge Validation

  • On the next live sync, confirm unchanged issues are no longer re-written every run (stats.pulled reflects only genuinely-changed issues, not the whole corpus).