Skip to content

Commit 607dfec

Browse files
author
GJC Multisession
committed
fix(discovery): compare contained-root identity at plain-stat precision
The user agent directory identity is captured with a plain fs.stat, whose dev/ino are doubles. readContainedFile widened them to bigint and compared against an exact bigint stat, so any NTFS file ID above 2^53 never matched and native user-agent instructions were silently skipped on Windows. The #4834 explicit-agent-dir case failed whenever the temp dir got such an ID.
1 parent 692f7fe commit 607dfec

3 files changed

Lines changed: 43 additions & 5 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Fixed
2+
3+
- Native user-agent instructions are no longer intermittently skipped on Windows when the agent directory's NTFS file ID exceeds 2^53: contained-file reads now compare the root identity in the same precision a plain `stat` captured it.

‎packages/coding-agent/src/discovery/helpers.ts‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,21 @@ export async function scanSkillsFromDir(
991991
return { items, warnings };
992992
}
993993

994+
/**
995+
* Compare a bigint-stat identity with one captured by a plain `fs.stat`.
996+
*
997+
* A plain stat reports `dev`/`ino` as doubles, so 64-bit values above 2^53
998+
* (common NTFS file IDs) are already rounded. Widening that rounded double to
999+
* a bigint can never equal the exact value; narrowing the exact value with the
1000+
* same IEEE rounding reproduces what the plain stat reported.
1001+
*/
1002+
export function bigintIdentityMatchesNumeric(
1003+
actual: { readonly dev: bigint; readonly ino: bigint },
1004+
expected: FileIdentity,
1005+
): boolean {
1006+
return Number(actual.dev) === expected.dev && Number(actual.ino) === expected.ino;
1007+
}
1008+
9941009
/** Read a regular file that remains contained by its configured root. */
9951010
export async function readContainedFile(
9961011
root: string,
@@ -1003,11 +1018,7 @@ export async function readContainedFile(
10031018
const capturedRoot = await captureDirectoryIdentity(root, true);
10041019
if (!capturedRoot) return null;
10051020
rootIdentity = capturedRoot;
1006-
if (
1007-
expectedRootIdentity !== undefined &&
1008-
(rootIdentity.dev !== BigInt(expectedRootIdentity.dev) ||
1009-
rootIdentity.ino !== BigInt(expectedRootIdentity.ino))
1010-
)
1021+
if (expectedRootIdentity !== undefined && !bigintIdentityMatchesNumeric(rootIdentity, expectedRootIdentity))
10111022
return null;
10121023
await SkillDiscoveryTestHooks.afterContainedRootValidated?.(root);
10131024
if (!(await directoryIdentityIsCurrent(root, rootIdentity, true))) return null;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { describe, expect, it } from "bun:test";
2+
import { bigintIdentityMatchesNumeric } from "../src/discovery/helpers";
3+
4+
describe("bigintIdentityMatchesNumeric", () => {
5+
it("matches a 64-bit file ID that a plain stat rounded to a double", () => {
6+
// NTFS file IDs routinely exceed 2^53; a plain fs.stat reports the rounded double.
7+
const exact = { dev: 3_430_812_034n, ino: 2n ** 60n + 12_345n };
8+
const plainStat = { dev: Number(exact.dev), ino: Number(exact.ino) };
9+
10+
expect(BigInt(plainStat.ino)).not.toBe(exact.ino);
11+
expect(bigintIdentityMatchesNumeric(exact, plainStat)).toBe(true);
12+
});
13+
14+
it("rejects a different directory", () => {
15+
const exact = { dev: 7n, ino: 42n };
16+
expect(bigintIdentityMatchesNumeric(exact, { dev: 7, ino: 43 })).toBe(false);
17+
expect(bigintIdentityMatchesNumeric(exact, { dev: 8, ino: 42 })).toBe(false);
18+
});
19+
20+
it("rejects a large file ID beyond double rounding distance", () => {
21+
const exact = { dev: 1n, ino: 2n ** 60n };
22+
expect(bigintIdentityMatchesNumeric(exact, { dev: 1, ino: Number(2n ** 60n + 4096n) })).toBe(false);
23+
});
24+
});

0 commit comments

Comments
 (0)