Skip to content
Closed
Show file tree
Hide file tree
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
8 changes: 8 additions & 0 deletions nemoclaw/src/blueprint/state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ describe("blueprint/state", () => {
store.set(STATE_PATH, JSON.stringify(saved));
expect(loadState()).toEqual(saved);
});

it("returns blank state when file contains malformed JSON", () => {
store.set(STATE_PATH, "not valid json {{{");
const state = loadState();
expect(state.lastRunId).toBeNull();
expect(state.lastAction).toBeNull();
expect(state.updatedAt).toBeDefined();
});
});

describe("saveState", () => {
Expand Down
6 changes: 5 additions & 1 deletion nemoclaw/src/blueprint/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ export function loadState(): NemoClawState {
if (!existsSync(path)) {
return blankState();
}
return JSON.parse(readFileSync(path, "utf-8")) as NemoClawState;
try {
return JSON.parse(readFileSync(path, "utf-8")) as NemoClawState;
} catch {
return blankState();
}
}

export function saveState(state: NemoClawState): void {
Expand Down
10 changes: 10 additions & 0 deletions nemoclaw/src/commands/migration-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,16 @@ describe("commands/migration-state", () => {
const loaded = loadSnapshotManifest("/snapshots/snap1");
expect(loaded).toEqual(manifest);
});

it("throws when snapshot.json contains malformed JSON", () => {
addFile("/snapshots/bad/snapshot.json", "not valid json");
expect(() => loadSnapshotManifest("/snapshots/bad")).toThrow();
});

it("throws when snapshot.json is an array instead of object", () => {
addFile("/snapshots/bad/snapshot.json", "[1, 2, 3]");
expect(() => loadSnapshotManifest("/snapshots/bad")).toThrow(/not a JSON object/);
});
});

describe("restoreSnapshotToHost", () => {
Expand Down
9 changes: 6 additions & 3 deletions nemoclaw/src/commands/migration-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,9 +484,12 @@ function writeSnapshotManifest(snapshotDir: string, manifest: SnapshotManifest):
}

function readSnapshotManifest(snapshotDir: string): SnapshotManifest {
return JSON.parse(
readFileSync(path.join(snapshotDir, "snapshot.json"), "utf-8"),
) as SnapshotManifest;
const filePath = path.join(snapshotDir, "snapshot.json");
const parsed: unknown = JSON.parse(readFileSync(filePath, "utf-8"));
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error(`Snapshot manifest at ${filePath} is not a JSON object.`);
}
return parsed as SnapshotManifest;
}

function resolveConfigSourcePath(manifest: SnapshotManifest, snapshotDir: string): string {
Expand Down
6 changes: 6 additions & 0 deletions nemoclaw/src/onboard/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ describe("onboard/config", () => {
store.set(configPath, JSON.stringify(config));
expect(loadOnboardConfig()).toEqual(config);
});

it("returns null when config file contains malformed JSON", () => {
const configPath = `${process.env.HOME ?? "/tmp"}/.nemoclaw/config.json`;
store.set(configPath, "corrupt data here");
expect(loadOnboardConfig()).toBeNull();
});
});

describe("saveOnboardConfig", () => {
Expand Down
6 changes: 5 additions & 1 deletion nemoclaw/src/onboard/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ export function loadOnboardConfig(): NemoClawOnboardConfig | null {
if (!existsSync(path)) {
return null;
}
return JSON.parse(readFileSync(path, "utf-8")) as NemoClawOnboardConfig;
try {
return JSON.parse(readFileSync(path, "utf-8")) as NemoClawOnboardConfig;
} catch {
return null;
}
}

export function saveOnboardConfig(config: NemoClawOnboardConfig): void {
Expand Down