fix(usage-log): reject non-array token logs instead of resetting history
readJsonArray treated any valid JSON that is not an array as [], causing appendRecord to overwrite the file with only the new entry — silently deleting all prior data. This is the same data-loss mode the malformed-JSON fix was trying to prevent. Fix: throw ERR_UNEXPECTED_TOKEN_LOG_SHAPE when parsed JSON is not an array so appendRecord aborts and the existing file is preserved.
This commit is contained in:
parent
f267ff7888
commit
a7a7923d09
@ -120,6 +120,24 @@ describe("recordTokenUsage", () => {
|
||||
expect(records[0].outputTokens).toBe(50);
|
||||
});
|
||||
|
||||
it("does not overwrite a valid-but-non-array token-usage.json — rejects unexpected shape", async () => {
|
||||
// Simulate a manual edit or migration that left a valid JSON object
|
||||
await fs.mkdir(path.join(tmpDir, "memory"), { recursive: true });
|
||||
await fs.writeFile(usageFile, '{"legacy": true, "records": []}', "utf-8");
|
||||
|
||||
await expect(
|
||||
recordTokenUsage({
|
||||
workspaceDir: tmpDir,
|
||||
label: "llm_output",
|
||||
usage: { input: 100, output: 50, total: 150 },
|
||||
}),
|
||||
).rejects.toThrow("not an array");
|
||||
|
||||
// File must be unchanged — the legacy data is preserved.
|
||||
const content = await fs.readFile(usageFile, "utf-8");
|
||||
expect(content).toBe('{"legacy": true, "records": []}');
|
||||
});
|
||||
|
||||
it("does not overwrite a malformed token-usage.json — preserves corrupted file", async () => {
|
||||
// Simulate an interrupted write that left partial JSON
|
||||
await fs.mkdir(path.join(tmpDir, "memory"), { recursive: true });
|
||||
|
||||
@ -27,7 +27,18 @@ async function readJsonArray(file: string): Promise<TokenUsageRecord[]> {
|
||||
try {
|
||||
const raw = await fs.readFile(file, "utf-8");
|
||||
const parsed = JSON.parse(raw);
|
||||
return Array.isArray(parsed) ? (parsed as TokenUsageRecord[]) : [];
|
||||
if (!Array.isArray(parsed)) {
|
||||
// Valid JSON but unexpected shape (object, number, string, …).
|
||||
// Returning [] here would cause appendRecord to overwrite the file
|
||||
// with only the new entry, silently deleting prior data.
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
`token-usage.json contains valid JSON but is not an array (got ${typeof parsed})`,
|
||||
),
|
||||
{ code: "ERR_UNEXPECTED_TOKEN_LOG_SHAPE" },
|
||||
);
|
||||
}
|
||||
return parsed as TokenUsageRecord[];
|
||||
} catch (err) {
|
||||
// File does not exist yet — start with an empty array.
|
||||
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user