Summary
In sea-loader.js, the local version comparison helper fi(e, t) uses e.localeCompare(t) to compare prerelease / build tag suffixes when major/minor/patch segments match.
Because "1.0.81-9".localeCompare("1.0.81-11") returns 1 (lexicographical sorting where '9' > '1'), the bootstrap loader determines that 1.0.81-9 is newer than 1.0.81-10 or 1.0.81-11. Consequently, even after copilot update successfully downloads and extracts 1.0.81-11 into the package cache (%LOCALAPPDATA%\copilot\pkg\win32-x64\1.0.81-11), launching copilot continues to execute the cached 1.0.81-9 version.
Root Cause Analysis
In sea-loader.js:
function fi(e, t) {
let i = kr(e), r = kr(t);
if (!i && !r) return 0;
if (!i) return -1;
if (!r) return 1;
for (let o = 0; o < 3; o++) {
if (i[o] !== r[o]) return i[o] - r[o];
}
let s = e.includes("-"), n = t.includes("-");
return s !== n ? (s ? -1 : 1) : e.localeCompare(t);
}
kr() parses the first 3 dot-delimited integer segments [major, minor, patch].
- When comparing
1.0.81-9 and 1.0.81-11, the numeric segments [1, 0, 81] match.
- Both strings include
-, so s !== n is false.
- The fallback is
e.localeCompare(t).
"1.0.81-9".localeCompare("1.0.81-11") evaluates to 1 (since '9' > '1').
- In
mi("index.js", ...):
i.sort((r, s) => {
let n = fi(basename(s), basename(r));
return n !== 0 ? n : ...;
});
The version list is sorted descending using fi(), placing 1.0.81-9 at the top before 1.0.81-11.
hh() finds 1.0.81-9 and executes its index.js.
Steps to Reproduce
- Have both
1.0.81-9 and 1.0.81-11 in %LOCALAPPDATA%\copilot\pkg\win32-x64\.
- Run
copilot --version.
- Observed: Outputs
GitHub Copilot CLI 1.0.81-9..
- Explicitly passing
--prefer-version 1.0.81-11 (copilot --prefer-version 1.0.81-11 --version) outputs GitHub Copilot CLI 1.0.81-11..
- Renaming/removing
1.0.81-9 immediately allows default copilot --version to output GitHub Copilot CLI 1.0.81-11..
Suggested Fix
Implement SemVer-compliant prerelease identifier comparison in fi() by splitting the prerelease tag (e.g. on . or -) and comparing numeric identifiers as integers and string identifiers lexicographically:
function comparePrerelease(a, b) {
const parsePart = (p) => /^\d+$/.test(p) ? parseInt(p, 10) : p;
const partsA = a.replace(/^[^-]*-/, '').split('.').map(parsePart);
const partsB = b.replace(/^[^-]*-/, '').split('.').map(parsePart);
const len = Math.max(partsA.length, partsB.length);
for (let i = 0; i < len; i++) {
if (partsA[i] === undefined) return -1;
if (partsB[i] === undefined) return 1;
if (partsA[i] === partsB[i]) continue;
if (typeof partsA[i] === 'number' && typeof partsB[i] === 'number') {
return partsA[i] - partsB[i];
}
return String(partsA[i]).localeCompare(String(partsB[i]));
}
return 0;
}
Environment
- Platform: Windows 11 (
win32-x64)
- Copilot CLI:
1.0.81-11 binary loading 1.0.81-9 cached package
Summary
In
sea-loader.js, the local version comparison helperfi(e, t)usese.localeCompare(t)to compare prerelease / build tag suffixes when major/minor/patch segments match.Because
"1.0.81-9".localeCompare("1.0.81-11")returns1(lexicographical sorting where'9' > '1'), the bootstrap loader determines that1.0.81-9is newer than1.0.81-10or1.0.81-11. Consequently, even aftercopilot updatesuccessfully downloads and extracts1.0.81-11into the package cache (%LOCALAPPDATA%\copilot\pkg\win32-x64\1.0.81-11), launchingcopilotcontinues to execute the cached1.0.81-9version.Root Cause Analysis
In
sea-loader.js:kr()parses the first 3 dot-delimited integer segments[major, minor, patch].1.0.81-9and1.0.81-11, the numeric segments[1, 0, 81]match.-, sos !== nisfalse.e.localeCompare(t)."1.0.81-9".localeCompare("1.0.81-11")evaluates to1(since'9' > '1').mi("index.js", ...):fi(), placing1.0.81-9at the top before1.0.81-11.hh()finds1.0.81-9and executes itsindex.js.Steps to Reproduce
1.0.81-9and1.0.81-11in%LOCALAPPDATA%\copilot\pkg\win32-x64\.copilot --version.GitHub Copilot CLI 1.0.81-9..--prefer-version 1.0.81-11(copilot --prefer-version 1.0.81-11 --version) outputsGitHub Copilot CLI 1.0.81-11..1.0.81-9immediately allows defaultcopilot --versionto outputGitHub Copilot CLI 1.0.81-11..Suggested Fix
Implement SemVer-compliant prerelease identifier comparison in
fi()by splitting the prerelease tag (e.g. on.or-) and comparing numeric identifiers as integers and string identifiers lexicographically:Environment
win32-x64)1.0.81-11binary loading1.0.81-9cached package