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)
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.Z → vX.Y.Z shift (real release-boundary recalculation, e.g. issue #7910 v11.12.0 → v11.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) {
const oldIsValidTag = semver.valid(semver.clean(issue.oldVersion)) !== null;
const newIsValidTag = semver.valid(semver.clean(version)) !== null;
if (oldIsValidTag && newIsValidTag) {
logger.warn(`🚨 [ARCHIVE ANOMALY] Issue #${issue.number} closedAt shift detected: moving from bucket '${issue.oldVersion}' to '${version}'. Dry-run review required.`);
} else {
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:
<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
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.Z → vX.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
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-droppedevents to DEBUG, operator re-rannpm run ai:sync-github-workflowto 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 aterrorlevel, which loses real errors).Empirical evidence
Operator 2026-05-16T19:33Z
npm run ai:sync-github-workflowpaste (SIGINT after thousands of lines):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.`); }oldVersionis derived (line 300-307) from the cached path's directory name. During ADR 0004's clean-cut, every historically-archived closed issue has anoldVersionfrom the pre-cut naming scheme (title-derived strings prefixed withv, e.g.'vneo.d.ts - Typescript definitions for all neo framework classes'), while the new time-based resolution returns avX.Y.Zrelease 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.Z→vX.Y.Zshift (real release-boundary recalculation, e.g. issue #7910v11.12.0→v11.13.0). The other 999/1000 cases are migration-state false positives.Bug 2:
#planBucketscalled twice per sync (lines 547 + 982)#planBucketshas 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. Usesemverlibrary (already inpackage.jsonas"^7.7.4", precedent:HealthService.mjs:416usessemver.gte).Change 1: Filter the WARN to fire only when both
oldVersionANDversionparse as valid semver tagsAdd 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 andvprefix;semver.valid()returns the canonical version string for valid semver inputs,nullotherwise. 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
#planBucketscall sitesAdd 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
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.0should fire WARN (both valid semver persemver.valid).#planBucketstwice in a test harness, asserts only one WARN emitted; clearing#warnedAnomaliesbetween sync cycles re-enables warnings.npm run ai:sync-github-workflowpost-merge — WARN volume bounded to genuine semver-tag → semver-tag shifts (operator paste showed only 1 such case: #7910), single-emit per issue.--verboseflag exposes the migration-state DEBUG events for diagnostic use.Avoided traps
/^v\d+\.\d+\.\d+$/: rejected per operator hint —semverlibrary is already inpackage.jsonand handles pre-release tags + build metadata correctly; regex would missv8.1.0-rc.1,v8.1.0+build.123, and any future tag-format evolution.vX.Y.Z→vX.Y.Zshifts (e.g. #7910) ARE actionable and the operator should see them; only the migration-state false positives should be quieted.[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.oldVersionstrings: 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.Related
semver@^7.7.4already inpackage.json; precedent usage inai/services/github-workflow/HealthService.mjs:416