LearnNewsExamplesServices
Frontmatter
id12692
titleHydrate cached release tags for sync_all fast path
stateClosed
labels
bugairegressionperformance
assigneesneo-gpt
createdAtJun 7, 2026, 8:12 PM
updatedAtJun 7, 2026, 9:07 PM
githubUrlhttps://github.com/neomjs/neo/issues/12692
authorneo-gpt
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 7, 2026, 8:44 PM

Hydrate cached release tags for sync_all fast path

Closed v13.0.0/archive-v13-0-0-chunk-16 bugairegressionperformance
neo-gpt
neo-gpt commented on Jun 7, 2026, 8:12 PM

Context

The operator reported that sync_all became extremely slow and appeared to fetch the full release-note history every run. After a machine restart the original logs were gone, but the remembered symptom was that cached releases were not identified and the log showed an undefined cached value.

Verify-before-assert evidence from this session confirms that symptom:

  • resources/content/.sync-metadata.json is warm and currently contains 1194 cached releases with releasesLastFetched: 2026-06-07T17:26:07.535Z.
  • The latest cached entry is keyed by 12.1.0, but its persisted value only has publishedAt and contentHash; it does not have tagName.
  • ai/services/github-workflow/sync/ReleaseNotesSyncer.mjs builds the warm-cache array from Object.values(cachedReleases), then compares latestRelease.tagName === cachedLatest.tagName.
  • ai/services/github-workflow/sync/MetadataManager.mjs intentionally prunes release metadata to { publishedAt, contentHash }, so the warm-cache comparison sees cachedLatest.tagName === undefined for persisted metadata.
  • Existing warm-cache coverage in test/playwright/unit/ai/services/github-workflow/ReleaseNotesSyncer.spec.mjs uses fixtures where each cached release value still includes tagName, so it does not exercise the real persisted shape.

Live latest-open sweep: checked the latest 20 open GitHub issues on 2026-06-07 and found no equivalent ticket. Exact open searches for ReleaseNotesSyncer tagName undefined full release pagination and sync_all cached releases undefined returned no results. Knowledge Base and Memory Core searches for ReleaseNotesSyncer warm cache, sync_all slow release notes fetch every run, and release metadata warm cache regression found no prior session that already mapped this specific persisted-metadata bug.

The Problem

sync_all has a release-cache fast path intended to avoid fetching the full release history when the latest GitHub release matches the latest cached release. The persisted metadata shape prevents that fast path from recognizing a warm cache:

  1. MetadataManager.save() prunes release objects to keep resources/content/.sync-metadata.json small.
  2. The release tag survives only as the map key, not as value.tagName.
  3. ReleaseNotesSyncer.fetchAndCacheReleases() discards the key by using Object.values().
  4. The latest-release comparison fails because the cached value's tagName is undefined.
  5. The syncer falls back to full release pagination across roughly 1200 releases.

There is a second trap: if the implementation only restores tagName, the warm path can proceed into syncNotes(), where pruned cached releases still lack name and description. That must not regenerate corrupt markdown like # undefined or rewrite release notes from incomplete metadata.

The Architectural Reality

  • ai/services/github-workflow/sync/ReleaseNotesSyncer.mjs#fetchAndCacheReleases owns the release-history fetch and warm-cache fast path.
  • ai/services/github-workflow/sync/ReleaseNotesSyncer.mjs#syncNotes owns release-note markdown/index generation and currently expects full release bodies when iterating this.releases.
  • ai/services/github-workflow/sync/MetadataManager.mjs#save is the source of authority for the persisted sync metadata shape and intentionally prunes releases to publishedAt plus contentHash.
  • resources/content/.sync-metadata.json demonstrates the production shape: release tag in the object key, not in the release value.
  • test/playwright/unit/ai/services/github-workflow/ReleaseNotesSyncer.spec.mjs has the relevant warm-cache test but currently fixtures the pre-pruned shape.

The Fix

Preserve the pruned metadata contract, and make ReleaseNotesSyncer hydrate cached release identity from the release map key:

  1. In fetchAndCacheReleases(metadata), build the cached release list from Object.entries(metadata.releases || {}) so each hydrated cached item gets tagName from the key plus publishedAt and contentHash from the value.
  2. Compare the latest remote release against that hydrated cached latest entry.
  3. Populate sortedReleases from hydrated cached entries so downstream closed-item bucketing receives valid { tagName, publishedAt } pairs without full pagination.
  4. Ensure syncNotes(metadata) does not regenerate or overwrite markdown for pruned cached release entries that lack enough body data. Unchanged cached entries should be skipped from content generation while keeping their index metadata valid.
  5. Update unit coverage to use the persisted/pruned metadata shape, not the pre-pruned in-memory shape.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
MetadataManager.save() release metadata ai/services/github-workflow/sync/MetadataManager.mjs Continue persisting release values as { publishedAt, contentHash }; do not bloat metadata with descriptions If pruning changes, this ticket must be re-scoped because metadata-size behavior changes Existing JSDoc plus test fixture comments Unit test verifies warm cache from pruned metadata
ReleaseNotesSyncer.fetchAndCacheReleases(metadata) ai/services/github-workflow/sync/ReleaseNotesSyncer.mjs Hydrate tagName from cached release map keys and take the one-query warm path when latest release matches On mismatch or latest-query failure, retain existing full-history fetch Inline comments should explain key hydration Unit test asserts one latest-release query and no full pagination
ReleaseNotesSyncer.sortedReleases ReleaseNotesSyncer cache contract consumed by issue/PR/discussion release bucketing Warm-cache path provides valid tagName and publishedAt for all cached releases Full fetch continues to populate from GitHub nodes Existing comments about full-history bucketing remain valid Unit test asserts no undefined tagName entries
ReleaseNotesSyncer.syncNotes(metadata) ai/services/github-workflow/sync/ReleaseNotesSyncer.mjs Do not rewrite release-note markdown from pruned cached entries missing name or description; skip unchanged cached entries safely Full fetched release bodies still generate markdown as before Add a short comment at the guard if needed Unit test proves no corrupt # undefined write on pruned warm cache

Decision Record Impact

none.

Acceptance Criteria

  • Warm-cache persisted metadata with release tags only in object keys is recognized as up-to-date when the latest remote release matches the latest cached key and publishedAt.
  • Warm-cache path performs only the latest-release query and does not fall back to full FETCH_RELEASES pagination when unchanged.
  • sortedReleases contains valid tagName and publishedAt entries on the warm path; no cached release has tagName: undefined.
  • syncNotes() does not regenerate or overwrite release-note markdown from pruned cached release entries that lack full body fields; no # undefined release note can be produced by the warm path.
  • Unit coverage uses the persisted/pruned metadata shape and fails against the current implementation.
  • Post-fix sync_all no-op validation shows releases are up to date and avoids the full release-history fetch.

Out of Scope

  • Lowering maxReleases or reducing the full-history requirement. Full history is still needed for release bucketing when the cache is cold or stale.
  • Persisting all release descriptions in .sync-metadata.json.
  • Reworking release-note archive bucketing.
  • Creating the release-workflow or release-notes skill. That remains adjacent to #10321.
  • Multi-body grid selection-model architecture.

Avoided Traps

  • Do not solve this by persisting full release bodies in metadata; that trades the slow fetch for persistent metadata bloat.
  • Do not lower the release cap as a performance workaround; that risks reintroducing bad closed-item bucketing for old issues and PRs.
  • Do not trust the existing warm-cache test as sufficient; it uses a shape that MetadataManager.save() does not persist.
  • Do not only hydrate tagName and ignore syncNotes(); that can turn a performance fix into release-note corruption.

Related

Origin Session ID

Origin Session ID: e8f07ef9-ef7e-4815-8ff4-7abe13720621

Handoff Retrieval Hints

  • query_raw_memories(query="sync_all ReleaseNotesSyncer cached releases undefined tagName full release pagination")
  • query_summaries(query="release metadata warm cache regression")
  • Source anchors: ai/services/github-workflow/sync/ReleaseNotesSyncer.mjs, ai/services/github-workflow/sync/MetadataManager.mjs, resources/content/.sync-metadata.json, test/playwright/unit/ai/services/github-workflow/ReleaseNotesSyncer.spec.mjs.
tobiu closed this issue on Jun 7, 2026, 8:44 PM
tobiu referenced in commit 3193b91 - "fix(sync): hydrate cached release tags (#12692) (#12693)" on Jun 7, 2026, 8:44 PM