Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ const syncManifestPath = `./data/launchdarkly-migrations/sync-manifest-${inputAr
let previousManifest: SyncManifest | null = null;
const updatedManifestFlags: Record<string, SyncManifestFlag> = {};
const updatedManifestSegments: Record<string, Record<string, SyncManifestSegment>> = {};
const segmentsWithErrors: Set<string> = new Set();
let incrementalSkipCount = 0;
let incrementalEnvSkipCount = 0;
let incrementalSegmentSkipCount = 0;
Expand Down Expand Up @@ -710,15 +711,19 @@ if (inputArgs.migrateSegments) {
patchRules.status,
`Patching segment ${segmentKey} status: ${segPatchStatus}`,
);
}

// Track segment version for sync manifest only if it was actually created/patched
if (segmentCreated) {
// Track segment version for sync manifest only when created/patched successfully
const segmentPatchOk = patchRules.status >= 200 && patchRules.status < 300;
if (segmentPatchOk) {
if (!updatedManifestSegments[env.key]) updatedManifestSegments[env.key] = {};
updatedManifestSegments[env.key][segment.key] = {
Comment on lines +715 to 719
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

segmentPatchOk is derived solely from the PATCH response status. If sgmtPatches is empty (possible when the segment has no included/excluded/rules/contexts), the PATCH request may be unnecessary and could fail (e.g., 400), which would incorrectly classify the segment as an error and permanently omit it from the manifest. Consider short-circuiting when there are no patch operations (treat as success and/or skip the PATCH call) so manifest tracking remains accurate and reruns don’t repeatedly retry harmless segments.

Copilot uses AI. Check for mistakes.
version: segment.version ?? 0,
lastModified: segment.lastModifiedDate,
};
} else {
segmentsWithErrors.add(`${env.key}:${segment.key}`);
console.log(Colors.gray(` → Skipped manifest update for segment ${segment.key} (patch failed; will retry next run)`));
}
}
};
};
Expand Down Expand Up @@ -1683,12 +1688,16 @@ for (const [index, flagkey] of flagList.entries()) {
}
}

// Track flag version for sync manifest (flag-level lastModified comes from creationDate or _version)
updatedManifestFlags[flag.key] = {
version: flag._version ?? 0,
lastModified: flag.creationDate,
environments: flagManifestEnvs,
};
// Track flag version for sync manifest only when successfully created/updated (no errors)
if (flagCreated && !flagsWithErrors.has(flag.key)) {
updatedManifestFlags[flag.key] = {
version: flag._version ?? 0,
lastModified: flag.creationDate,
environments: flagManifestEnvs,
};
} else if (flagsWithErrors.has(flag.key)) {
Comment on lines +1692 to +1698
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

flagsWithErrors is populated with the destination flag key (createdFlagKey) in several error paths (e.g., failed flag-level patch / env patches), but the manifest gating here checks flagsWithErrors.has(flag.key) (the source key). If a conflict prefix was applied, errors won’t be detected and the source flag will be written to the manifest anyway, causing --incremental runs to skip retries. Align the key used here with what’s stored in flagsWithErrors (e.g., consistently track errors by source key, or check createdFlagKey when deciding whether to write updatedManifestFlags[flag.key]).

Suggested change
if (flagCreated && !flagsWithErrors.has(flag.key)) {
updatedManifestFlags[flag.key] = {
version: flag._version ?? 0,
lastModified: flag.creationDate,
environments: flagManifestEnvs,
};
} else if (flagsWithErrors.has(flag.key)) {
// flagsWithErrors is tracked by destination key, so gate manifest writes using createdFlagKey.
if (flagCreated && !flagsWithErrors.has(createdFlagKey)) {
updatedManifestFlags[flag.key] = {
version: flag._version ?? 0,
lastModified: flag.creationDate,
environments: flagManifestEnvs,
};
} else if (flagsWithErrors.has(createdFlagKey)) {

Copilot uses AI. Check for mistakes.
console.log(Colors.gray(`\t → Skipped manifest update for ${flag.key} (errors occurred; will retry next run)`));
}
}

// Always write sync manifest so --incremental works on next run
Expand All @@ -1710,6 +1719,13 @@ try {
console.log(Colors.yellow(`\n⚠ Could not write sync manifest: ${error instanceof Error ? error.message : String(error)}`));
}

if (flagsWithErrors.size > 0 || segmentsWithErrors.size > 0) {
const parts = [];
if (flagsWithErrors.size > 0) parts.push(`${flagsWithErrors.size} flag(s)`);
if (segmentsWithErrors.size > 0) parts.push(`${segmentsWithErrors.size} segment(s)`);
console.log(Colors.yellow(`\n⚠ ${parts.join(', ')} omitted from manifest due to errors (will retry next run)`));
}

if (incrementalSkipCount > 0 || incrementalEnvSkipCount > 0 || incrementalSegmentSkipCount > 0) {
const parts = [];
if (incrementalSkipCount > 0) parts.push(`${incrementalSkipCount} flag(s)`);
Expand Down