From a971641a549e250ec91ee29c1656c883392c1fc2 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 20 Jun 2026 03:15:30 +0200 Subject: [PATCH] fix(test): guard claude usage debug args --- scripts/debug-claude-usage.ts | 82 ++++++++++++++++++++++--- test/scripts/dev-tooling-safety.test.ts | 54 ++++++++++++++++ 2 files changed, 126 insertions(+), 10 deletions(-) diff --git a/scripts/debug-claude-usage.ts b/scripts/debug-claude-usage.ts index d9676395fe3..a6a6f84975e 100644 --- a/scripts/debug-claude-usage.ts +++ b/scripts/debug-claude-usage.ts @@ -16,6 +16,7 @@ import { type Args = { agentId: string; + help: boolean; reveal: boolean; sessionKey?: string; }; @@ -36,31 +37,86 @@ const mask = (value: string) => { ); }; -const parseArgs = (): Args => { - const args = process.argv.slice(2); +const parseArgs = (args = process.argv.slice(2)): Args => { let agentId = "main"; + let help = false; let reveal = false; let sessionKey: string | undefined; for (let i = 0; i < args.length; i++) { const arg = args[i]; - if (arg === "--agent" && args[i + 1]) { - agentId = args[++i].trim() || "main"; + if (arg === "--agent") { + agentId = parseNonBlankArgValue(parseRequiredArgValue(args, i, "--agent"), "--agent"); + i += 1; + continue; + } + if (arg.startsWith("--agent=")) { + agentId = parseNonBlankArgValue(parseInlineArgValue(arg, "--agent"), "--agent"); + continue; + } + if (arg === "--help" || arg === "-h") { + help = true; continue; } if (arg === "--reveal") { reveal = true; continue; } - if (arg === "--session-key" && args[i + 1]) { - sessionKey = normalizeOptionalString(args[++i]); + if (arg === "--session-key") { + sessionKey = parseNonBlankArgValue( + parseRequiredArgValue(args, i, "--session-key"), + "--session-key", + ); + i += 1; continue; } + if (arg.startsWith("--session-key=")) { + sessionKey = parseNonBlankArgValue( + parseInlineArgValue(arg, "--session-key"), + "--session-key", + ); + continue; + } + throw new Error(`Unknown argument: ${arg}`); } - return { agentId, reveal, sessionKey }; + return { agentId, help, reveal, sessionKey }; }; +function parseRequiredArgValue(args: string[], index: number, label: string): string { + const value = args[index + 1]; + if (!value || value.startsWith("-")) { + throw new Error(`${label} requires a value`); + } + return value; +} + +function parseInlineArgValue(arg: string, label: string): string { + const value = arg.slice(`${label}=`.length); + if (!value) { + throw new Error(`${label} requires a value`); + } + return value; +} + +function parseNonBlankArgValue(value: string, label: string): string { + const normalized = normalizeOptionalString(value); + if (!normalized) { + throw new Error(`${label} requires a value`); + } + return normalized; +} + +function printUsage(): void { + console.log(`Usage: node --import tsx scripts/debug-claude-usage.ts [options] + +Options: + --agent OpenClaw agent id to inspect (default: main) + --session-key Claude web session key override + --reveal Print token/session values instead of masked identifiers + --help, -h Show this help message`); +} + const loadAuthProfiles = (agentId: string) => { const stateRoot = process.env.OPENCLAW_STATE_DIR?.trim() || path.join(os.homedir(), ".openclaw"); const authPath = path.join(stateRoot, "agents", agentId, "agent", "auth-profiles.json"); @@ -413,8 +469,13 @@ const fetchClaudeWebUsage = async (sessionKey: string, options: FetchOptions = { : { ok: false as const, step: "usage", status: usageRes.status, body: usageText }; }; -const main = async () => { - const opts = parseArgs(); +const main = async (argv = process.argv.slice(2)) => { + const opts = parseArgs(argv); + if (opts.help) { + printUsage(); + return; + } + const { authPath, store } = loadAuthProfiles(opts.agentId); console.log(`Auth file: ${redactHomePath(authPath)}`); @@ -485,11 +546,12 @@ export const testing = { browserRootLabel, fetchAnthropicOAuthUsage, mask, + parseArgs, readBoundedResponseText, resolveFetchTimeoutMs, }; -if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { +if (import.meta.url === pathToFileURL(path.resolve(process.argv[1] ?? "")).href) { await main().catch((error: unknown) => { console.error( previewForDevToolLog(error instanceof Error ? error.message : String(error), 800), diff --git a/test/scripts/dev-tooling-safety.test.ts b/test/scripts/dev-tooling-safety.test.ts index 9669bc61b4f..38b873c1bea 100644 --- a/test/scripts/dev-tooling-safety.test.ts +++ b/test/scripts/dev-tooling-safety.test.ts @@ -716,6 +716,60 @@ describe("script-specific dev tooling hardening", () => { expect(claudeUsageTesting.CLAUDE_COOKIE_HOST_SQL).not.toContain("%claude.ai%"); }); + it("rejects malformed Claude usage args before reading auth or browser state", () => { + expect(claudeUsageTesting.parseArgs(["--agent", "work", "--session-key=abc"])).toEqual({ + agentId: "work", + help: false, + reveal: false, + sessionKey: "abc", + }); + expect(claudeUsageTesting.parseArgs(["--help"])).toEqual({ + agentId: "main", + help: true, + reveal: false, + sessionKey: undefined, + }); + expect(() => claudeUsageTesting.parseArgs(["--wat"])).toThrow("Unknown argument: --wat"); + expect(() => claudeUsageTesting.parseArgs(["--agent"])).toThrow("--agent requires a value"); + expect(() => claudeUsageTesting.parseArgs(["--agent="])).toThrow("--agent requires a value"); + expect(() => claudeUsageTesting.parseArgs(["--session-key", "--reveal"])).toThrow( + "--session-key requires a value", + ); + expect(() => claudeUsageTesting.parseArgs(["--session-key= "])).toThrow( + "--session-key requires a value", + ); + }); + + it("prints Claude usage help without opening auth stores", () => { + const result = spawnSync( + process.execPath, + ["--import", "tsx", "scripts/debug-claude-usage.ts", "--help"], + { + cwd: process.cwd(), + encoding: "utf8", + }, + ); + + expect(result.status).toBe(0); + expect(result.stdout).toContain("Usage: node --import tsx scripts/debug-claude-usage.ts"); + expect(result.stderr).toBe(""); + }); + + it("fails missing Claude usage option values before defaulting to main auth", () => { + const result = spawnSync( + process.execPath, + ["--import", "tsx", "scripts/debug-claude-usage.ts", "--agent"], + { + cwd: process.cwd(), + encoding: "utf8", + }, + ); + + expect(result.status).toBe(1); + expect(result.stdout).toBe(""); + expect(result.stderr).toContain("--agent requires a value"); + }); + it("aborts stalled Claude usage fetches at the request timeout", async () => { let signal: AbortSignal | undefined; const request = claudeUsageTesting.fetchAnthropicOAuthUsage("test-token", {