mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-10 11:41:37 -10:00
fix(daemon): keep spaced settings JSON whole when reading a Claude command line (#2665)
The Claude command-line reader keeps a --settings or --mcp-config JSON value whole when it's written with spaces, by tracking brace depth on those two options only, so a seat launched through a proxy alias with spaced settings can be verified and its conversation read (#2508). Other options' values aren't grouped. Contributed by Nikhi00718, with billbenetti's diagnosis.
This commit is contained in:
1 parent
4d7c50d514
commit
b40bc71ca6
2 files changed
+37
-5
No files matched your search
@@ -25,26 +25,37 @@ export type NativeRuntime = "claude-code" | "codex";
|
||||
function tokens(command: string): string[] {
|
||||
// ps flattens argv: inline settings JSON retains its string delimiters.
|
||||
// A quote inside a name or filename is literal, not a shell span delimiter.
|
||||
// A `--settings` or `--mcp-config` JSON value keeps the spaces between its members too, as in
|
||||
// `{"env": {"A": "b"}}`: it stays one token until its braces balance. Braces inside its strings don't count, and
|
||||
// a brace that opens any other option's value is literal (#2508).
|
||||
const result: string[] = [];
|
||||
let start = 0;
|
||||
let quote: string | null = null;
|
||||
let depth = 0;
|
||||
const append = (end: number) => {
|
||||
if (start === end) return;
|
||||
const token = command.slice(start, end);
|
||||
result.push(token[0] === '"' && token.at(-1) === '"'
|
||||
? token.slice(1, -1) : token);
|
||||
};
|
||||
const structured = () => command.startsWith('"{', start) || command[start] === "{"
|
||||
|| command.startsWith("'{", start) || command.startsWith("--settings={", start)
|
||||
|| command.startsWith('--settings="', start) || command.startsWith("--mcp-config={", start)
|
||||
|| command.startsWith('--mcp-config="', start);
|
||||
const jsonValue = () => command.startsWith("--settings={", start) || command.startsWith("--mcp-config={", start)
|
||||
|| (command[start] === "{" && (result.at(-1) === "--settings" || result.at(-1) === "--mcp-config"));
|
||||
for (let index = 0; index < command.length; index += 1) {
|
||||
const char = command[index]!;
|
||||
if (quote !== null) {
|
||||
if (quote === '"' && char === "\\") { index += 1; continue; }
|
||||
if (char === quote) quote = null;
|
||||
} else if (char === '"' && (command.startsWith('"{', start) || command[start] === "{"
|
||||
|| command.startsWith("'{", start) || command.startsWith("--settings={", start)
|
||||
|| command.startsWith('--settings="', start) || command.startsWith("--mcp-config={", start)
|
||||
|| command.startsWith('--mcp-config="', start))) {
|
||||
} else if (char === '"' && structured()) {
|
||||
quote = char;
|
||||
} else if (/\s/.test(char)) {
|
||||
} else if (char === "{" && jsonValue()) {
|
||||
depth += 1;
|
||||
} else if (char === "}" && depth > 0) {
|
||||
depth -= 1;
|
||||
} else if (/\s/.test(char) && depth === 0) {
|
||||
append(index);
|
||||
start = index + 1;
|
||||
}
|
||||
|
||||
@@ -483,6 +483,8 @@ describe("Claude identity with inline settings in ps output", () => {
|
||||
permissions: { allow: ["Bash(npm run test:*)"] },
|
||||
hooks: { PreToolUse: [{ hooks: [{ type: "command", command: 'node "/fixture with spaces/check.cjs" --label "it\'s fine"', timeout: 5 }] }] },
|
||||
})],
|
||||
// #2508: a proxy that writes its settings with spaces between JSON members, as ps shows the argument.
|
||||
["spaced JSON", '{"env": {"ANTHROPIC_BASE_URL": "http://127.0.0.1:8080", "ANTHROPIC_AUTH_TOKEN": "fixture"}}'],
|
||||
];
|
||||
function input(args: string) {
|
||||
const list = [
|
||||
@@ -513,6 +515,9 @@ describe("Claude identity with inline settings in ps output", () => {
|
||||
`--settings /fixture/settings.json --session-id ${token}`,
|
||||
`--settings /fixture/review's-settings.json --session-id ${token}`,
|
||||
`--settings /fixture/review"settings.json --session-id ${token}`,
|
||||
// #2508: a brace inside a spaced value's string doesn't end the value.
|
||||
`--settings {"env": {"note": "a } b {"}} --resume ${token}`,
|
||||
`--settings={"env": {"A": "b"}} --session-id=${token}`,
|
||||
])("accepts inline and file settings: %s", async args => {
|
||||
expect((await verifyClaudePaneProcess(input(args)))?.process.pid).toBe(21);
|
||||
expect((await observeClaudeDelivery(input(args))).state).toBe("verified");
|
||||
@@ -594,12 +599,28 @@ describe("Claude identity with inline settings in ps output", () => {
|
||||
it.each([
|
||||
`--settings ${JSON.stringify({ label: `two words --session-id ${token}` })}`,
|
||||
`--settings {"env":{"note":"text --session-id ${token} --model trailing`,
|
||||
// #2508: spaced settings stay one value, so an identity inside them, or after a value that never closes, proves nothing.
|
||||
`--settings {"env": {"note": "text --session-id ${token}"}}`,
|
||||
`--settings {"env": {"note": "x"} --session-id ${token}`,
|
||||
`--settings ${settings[0]![1]} --session-id ${token} --session-id different`,
|
||||
`--settings ${settings[1]![1]} --session-id ${token} --unknown`,
|
||||
])("does not promote ambiguous argv: %s", async args => {
|
||||
expect(await verifyClaudePaneProcess(input(args))).toBeNull();
|
||||
expect((await observeClaudeDelivery(input(args))).state).not.toBe("verified");
|
||||
});
|
||||
// #2508: braces group only a `--settings` or `--mcp-config` value. Any other value that opens with `{` still ends at
|
||||
// a space, so the options after it are read as on main.
|
||||
it("keeps a second resume after another option's brace-led value visible", async () => {
|
||||
const actual = input(`--resume ${token} --name {label --resume ${parent}`);
|
||||
expect(await verifyClaudePaneProcess(actual)).toBeNull();
|
||||
expect((await observeClaudeDelivery(actual)).state).not.toBe("verified");
|
||||
});
|
||||
it("reads the identity after another option's brace-led value", async () => {
|
||||
const actual = input(`--name {label --resume ${token}`);
|
||||
expect((await verifyClaudePaneProcess(actual))?.process.pid).toBe(21);
|
||||
expect((await observeClaudeDelivery(actual)).state).toBe("verified");
|
||||
expect(await verifyClaudePaneProcess({ ...actual, expectedToken: "different" })).toBeNull();
|
||||
});
|
||||
it.each([
|
||||
`--resume ${parent} --fork-session --session-id ${token}`,
|
||||
`--fork-session --session-id=${token} --resume=${parent}`,
|
||||
|
||||
Reference in new issue
Block a user