fix(cli): use tryCatch instead of tryCatchIf for JSON.parse callsites (#2770)

tryCatchIf(isFileError) only catches filesystem errors (ENOENT, EACCES),
but JSON.parse throws SyntaxError on corrupted input. Since tryCatchIf
rethrows non-matching errors, a corrupted config file crashes the CLI
instead of returning the intended null/false fallback.

Affected: readCache(), local manifest loader, loadApiToken(),
loadSavedOpenRouterKey(), hasCloudConfigCredentials()

Agent: code-health

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-03-18 12:54:41 -07:00 committed by GitHub
parent fc98700a24
commit 16a2f1807c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 9 additions and 9 deletions

View file

@ -11,7 +11,7 @@ import { validateIdentifier, validatePrompt } from "../security.js";
import { hasSavedOpenRouterKey } from "../shared/oauth.js";
import { PkgVersionSchema, parseJsonObj } from "../shared/parse.js";
import { getSpawnCloudConfigPath } from "../shared/paths.js";
import { asyncTryCatch, isFileError, tryCatch, tryCatchIf, unwrapOr } from "../shared/result.js";
import { asyncTryCatch, tryCatch, unwrapOr } from "../shared/result.js";
// ── Constants ────────────────────────────────────────────────────────────────
@ -507,7 +507,7 @@ export function formatCredStatusLine(varName: string, urlHint?: string): string
/** Check if credentials are saved in ~/.config/spawn/{cloud}.json */
function hasCloudConfigCredentials(cloud: string): boolean {
return unwrapOr(
tryCatchIf(isFileError, () => {
tryCatch(() => {
const configPath = getSpawnCloudConfigPath(cloud);
if (!fs.existsSync(configPath)) {
return false;

View file

@ -3,7 +3,7 @@ import { join } from "node:path";
import { getErrorMessage, isPlainObject } from "@openrouter/spawn-shared";
import { parseJsonObj } from "./shared/parse.js";
import { getCacheDir, getCacheFile } from "./shared/paths.js";
import { asyncTryCatch, isFileError, tryCatchIf, unwrapOr } from "./shared/result.js";
import { asyncTryCatch, isFileError, tryCatch, tryCatchIf, unwrapOr } from "./shared/result.js";
// ── Types ──────────────────────────────────────────────────────────────────────
@ -94,7 +94,7 @@ function logError(message: string, err?: unknown): void {
}
function readCache(): Manifest | null {
const result = tryCatchIf(isFileError, () => {
const result = tryCatch(() => {
const raw = parseJsonObj(readFileSync(getCacheFile(), "utf-8"));
if (!raw) {
return null;
@ -213,7 +213,7 @@ function tryLoadLocalManifest(): Manifest | null {
return null;
}
const result = tryCatchIf(isFileError, () => {
const result = tryCatch(() => {
const localPath = join(process.cwd(), "manifest.json");
if (existsSync(localPath)) {
const raw = parseJsonObj(readFileSync(localPath, "utf-8"));

View file

@ -7,7 +7,7 @@ import * as v from "valibot";
import { OAUTH_CODE_REGEX } from "./oauth-constants";
import { parseJsonObj, parseJsonWith } from "./parse";
import { getSpawnCloudConfigPath } from "./paths";
import { asyncTryCatchIf, isFileError, isNetworkError, tryCatch, tryCatchIf } from "./result.js";
import { asyncTryCatchIf, isFileError, isNetworkError, tryCatch } from "./result.js";
import { logDebug, logError, logInfo, logStep, logWarn, openBrowser, prompt } from "./ui";
// ─── Schemas ─────────────────────────────────────────────────────────────────
@ -286,7 +286,7 @@ export function hasSavedOpenRouterKey(): boolean {
/** Load a previously saved OpenRouter API key from ~/.config/spawn/openrouter.json. */
function loadSavedOpenRouterKey(): string | null {
const result = tryCatchIf(isFileError, () => {
const result = tryCatch(() => {
const configPath = getSpawnCloudConfigPath("openrouter");
const data = parseJsonObj(readFileSync(configPath, "utf-8"));
if (!data) {

View file

@ -6,7 +6,7 @@ import * as p from "@clack/prompts";
import { isString } from "@openrouter/spawn-shared";
import { parseJsonObj } from "./parse";
import { getSpawnCloudConfigPath } from "./paths";
import { asyncTryCatch, isFileError, tryCatch, tryCatchIf, unwrapOr } from "./result.js";
import { asyncTryCatch, tryCatch, unwrapOr } from "./result.js";
const RED = "\x1b[0;31m";
const GREEN = "\x1b[0;32m";
@ -242,7 +242,7 @@ export async function withRetry<T>(
*/
export function loadApiToken(cloud: string): string | null {
return unwrapOr(
tryCatchIf(isFileError, () => {
tryCatch(() => {
const data = parseJsonObj(readFileSync(getSpawnCloudConfigPath(cloud), "utf-8"));
if (!data) {
return null;