LearnNewsExamplesServices
Frontmatter
id11486
titleIssueSyncer ARCHIVE ANOMALY emits thousands of false-positive WARN during ADR 0004 clean-cut
stateClosed
labels
bugaimodel-experience
assigneesneo-opus-4-7
createdAtMay 16, 2026, 9:42 PM
updatedAtMay 16, 2026, 11:31 PM
githubUrlhttps://github.com/neomjs/neo/issues/11486
authorneo-opus-4-7
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 16, 2026, 11:31 PM

IssueSyncer ARCHIVE ANOMALY emits thousands of false-positive WARN during ADR 0004 clean-cut

Closedbugaimodel-experience
neo-opus-4-7
neo-opus-4-7 commented on May 16, 2026, 9:42 PM

FAIR-band: in-band [1/30] — operator-empirical friction during post-#11485 verification of npm run ai:sync-github-workflow; quick-win MX-friction reduction.

Premise

After PR #11485 downgraded per-item Created/Moved/Updated/Removed-dropped events to DEBUG, operator re-ran npm run ai:sync-github-workflow to verify CLI output was bounded to phase headers + summaries. Result: thousands of [WARN] lines flood the output during the ADR 0004 clean-cut window, completely defeating the visibility improvement #11485 delivered. The new noise tier is WARN-level archive anomalies, which --verbose-toggle can't suppress (the existing log-level filter only filters WARN at error level, which loses real errors).

Empirical evidence

Operator 2026-05-16T19:33Z npm run ai:sync-github-workflow paste (SIGINT after thousands of lines):

[WARN] 🚨 [ARCHIVE ANOMALY] Issue #3285 closedAt shift detected: moving from bucket 'vneo.d.ts - Typescript definitions for all neo framework classes' to 'v8.1.0'. Dry-run review required.
[WARN] 🚨 [ARCHIVE ANOMALY] Issue #3286 closedAt shift detected: moving from bucket 'vneo.d.ts - Typescript definitions for all neo framework classes' to 'v8.1.0'. Dry-run review required.
[WARN] 🚨 [ARCHIVE ANOMALY] Issue #3287 closedAt shift detected: moving from bucket 'vNeo-Material Component Library v0.1' to 'v8.1.0'. Dry-run review required.
[WARN] 🚨 [ARCHIVE ANOMALY] Issue #7910 closedAt shift detected: moving from bucket 'v11.12.0' to 'v11.13.0'. Dry-run review required.
... (each issue line appears TWICE in the stream; thousands of lines total)

Observation: each issue line appears exactly twice per sync.

Root cause

Two compounding bugs in ai/services/github-workflow/sync/IssueSyncer.mjs:

Bug 1: Bucket-name comparison fires on migration-state, not real anomalies (line 377)

// IssueSyncer.mjs:376-378 (#planBuckets)
if (issue.oldVersion && issue.oldVersion !== version) {
    logger.warn(`🚨 [ARCHIVE ANOMALY] Issue #${issue.number} closedAt shift detected: moving from bucket '${issue.oldVersion}' to '${version}'. Dry-run review required.`);
}

oldVersion is derived (line 300-307) from the cached path's directory name. During ADR 0004's clean-cut, every historically-archived closed issue has an oldVersion from the pre-cut naming scheme (title-derived strings prefixed with v, e.g. 'vneo.d.ts - Typescript definitions for all neo framework classes'), while the new time-based resolution returns a vX.Y.Z release tag. The mismatch fires WARN unconditionally.

Worse: the sealed-chunk enforcement at lines 569 + 575 already prevents these "shifts" from actually happening — the issue stays at its existing path. So this WARN reports a move that never occurs. Pure noise.

The ONLY case where this WARN is actionable is a vX.Y.ZvX.Y.Z shift (real release-boundary recalculation, e.g. issue #7910 v11.12.0v11.13.0). The other 999/1000 cases are migration-state false positives.

Bug 2: #planBuckets called twice per sync (lines 547 + 982)

#planBuckets has three call sites (lines 547, 783, 982). Lines 547 (main sync) and 982 (reconciliation) both walk the same merged-issue set and both fire the WARN per issue → exact 2× duplication observed in operator paste.

Prescription

Single PR, two coordinated changes in ai/services/github-workflow/sync/IssueSyncer.mjs. Use semver library (already in package.json as "^7.7.4", precedent: HealthService.mjs:416 uses semver.gte).

Change 1: Filter the WARN to fire only when both oldVersion AND version parse as valid semver tags

Add import at top of file (alongside existing imports):

import semver from 'semver';

Replace lines 376-378:

if (issue.oldVersion && issue.oldVersion !== version) {
    // semver.clean strips leading `v` and validates the remainder; null = not a real release tag.
    // Migration-state strings (title-derived garbage like 'vneo.d.ts - Typescript...') fail validation.
    const oldIsValidTag = semver.valid(semver.clean(issue.oldVersion)) !== null;
    const newIsValidTag = semver.valid(semver.clean(version)) !== null;
    if (oldIsValidTag && newIsValidTag) {
        // Real release-boundary recalculation (e.g. v11.12.0 → v11.13.0) — keep WARN
        logger.warn(`🚨 [ARCHIVE ANOMALY] Issue #${issue.number} closedAt shift detected: moving from bucket '${issue.oldVersion}' to '${version}'. Dry-run review required.`);
    } else {
        // Pre-clean-cut bucket name shape (title-derived garbage) → migration state, not anomaly.
        // Sealed-chunk semantics at L569/L575 already prevent actual movement.
        logger.debug(`[ARCHIVE MIGRATION] Issue #${issue.number}: oldBucket='${issue.oldVersion}' → newBucket='${version}' (sealed-chunk enforcement preserves location)`);
    }
}

semver.clean() strips leading whitespace and v prefix; semver.valid() returns the canonical version string for valid semver inputs, null otherwise. Robust against pre-release tags (v8.1.0-rc.1), build metadata (v8.1.0+build.123), and arbitrary garbage that a hand-rolled regex would miss.

Change 2: Dedupe across #planBuckets call sites

Add an instance-level Set tracking already-warned issues per sync cycle:

// In class body, alongside other fields
<h1 class="neo-h1" data-record-id="10">warnedAnomalies = new Set();</h1>

At sync orchestrator entry point (start of runFullSync() or equivalent), reset the set:

this.#warnedAnomalies.clear();

Guard the WARN emit:

if (oldIsValidTag && newIsValidTag && !this.#warnedAnomalies.has(issue.number)) {
    this.#warnedAnomalies.add(issue.number);
    logger.warn(`🚨 [ARCHIVE ANOMALY] ...`);
}

Verification

  • 12/12 syncer unit specs still pass (no spec asserts on log level; downgrade is invisible to existing tests)
  • New spec in IssueSyncer.spec.mjs: "ARCHIVE ANOMALY at WARN only when both buckets parse as valid semver tags" — covers issue #7910-shape (real anomaly, WARN expected) vs issue #3285-shape (migration shape, DEBUG only). Edge case: v8.1.0-rc.1 → v8.1.0 should fire WARN (both valid semver per semver.valid).
  • New spec: "Same issue not WARN'd twice per sync cycle" — runs #planBuckets twice in a test harness, asserts only one WARN emitted; clearing #warnedAnomalies between sync cycles re-enables warnings.
  • L4 operator verification: npm run ai:sync-github-workflow post-merge — WARN volume bounded to genuine semver-tag → semver-tag shifts (operator paste showed only 1 such case: #7910), single-emit per issue. --verbose flag exposes the migration-state DEBUG events for diagnostic use.

Avoided traps

  • Hand-rolled regex like /^v\d+\.\d+\.\d+$/: rejected per operator hint — semver library is already in package.json and handles pre-release tags + build metadata correctly; regex would miss v8.1.0-rc.1, v8.1.0+build.123, and any future tag-format evolution.
  • Removing the WARN entirely: rejected — real vX.Y.ZvX.Y.Z shifts (e.g. #7910) ARE actionable and the operator should see them; only the migration-state false positives should be quieted.
  • Suppressing all [ARCHIVE ANOMALY] log lines via a CLI flag: rejected — operator visibility into genuine anomalies must remain default-on; a flag would conceal a category of real bugs.
  • Adding a one-time migration script to rewrite cached oldVersion strings: rejected per ADR 0004's "no migration scripts. clean cut" mandate — the cached metadata will be naturally refreshed as the sync runs and writes new paths, so the noise self-resolves after one clean-cut cycle. The detector just needs to be quiet during that window.
  • Demoting the WARN to INFO: rejected — the WARN level is correct for the genuine-anomaly case (#7910-shape); demoting would lose the signal entirely.
  • Suppressing both occurrences of the same issue across the entire daemon lifetime: rejected — the dedupe set is reset per sync cycle so issue-shifts across separate sync runs still surface; we only suppress within a single sync.

Related

  • Parent: PR #11485 (per-item INFO→DEBUG sweep, merged 2026-05-16T19:21Z) — this is the natural FAIR follow-up: 11485 cleared INFO floor, this clears WARN floor
  • Empirical anchor: operator paste 2026-05-16T19:33Z showing thousands of WARN lines + SIGINT exit
  • Substrate parent: PR #11480 (logger filtering substrate — merged) — provides the level discipline this fix builds on
  • Substrate library: semver@^7.7.4 already in package.json; precedent usage in ai/services/github-workflow/HealthService.mjs:416
  • Adjacent: ADR 0004 clean-cut migration window — this PR is one of the resilience-during-migration fixes the operator's "no migration scripts" stance requires
tobiu referenced in commit b0a3a8d - "fix(github-workflow/sync): filter ARCHIVE ANOMALY false-positive WARN volume during ADR 0004 clean-cut (#11486) (#11488) on May 16, 2026, 11:31 PM
tobiu closed this issue on May 16, 2026, 11:31 PM