From 8ecb96e9fc49d3786df1d7901f19cf01c4041b7f Mon Sep 17 00:00:00 2001 From: nntrivi2001 Date: Mon, 12 Oct 2026 14:52:52 +0700 Subject: [PATCH] fix: a new account's first Claude session no longer opens on the bypass warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --allow-dangerously-skip-permissions (bypass in the pane's shift+tab cycle) made Claude Code show its Bypass Permissions warning on any account that had not accepted it yet — every new member's first session, which the Chat UI cannot answer, though nobody asked for bypass. Now passed only when the run starts in bypassPermissions (asking is consent to see it) or the account's user settings already hold skipDangerousModePermissionPrompt (pty-run bypassAccepted; a member's read owner-checked from their home). The host, which accepted long ago, keeps the flag. Test: pty-run. --- server/__tests__/pty-run.test.js | 22 +++++++++++++++++++++- server/lib/pty-run.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/server/__tests__/pty-run.test.js b/server/__tests__/pty-run.test.js index e3a492da..f5a5f819 100644 --- a/server/__tests__/pty-run.test.js +++ b/server/__tests__/pty-run.test.js @@ -374,7 +374,27 @@ describe("pty-run", () => { if (args[0] === "new-session") newSessionArgv = args; return ""; }); - pty.spawnRun({ laneId: 4, cwd: "/tmp/repo" }); + const prevHome = process.env.CLAUDE_HOME; + const home = fs.mkdtempSync(path.join(os.tmpdir(), "bypass-home-")); + process.env.CLAUDE_HOME = home; + try { + // An account that never accepted the bypass warning: no flag, so its + // first session does not open on that warning (the Chat UI can't answer). + pty.spawnRun({ laneId: 4, cwd: "/tmp/repo" }); + assert.ok(!newSessionArgv.includes("--allow-dangerously-skip-permissions")); + // ...unless the run asks for bypass itself: then the warning is wanted. + pty.spawnRun({ laneId: 4, cwd: "/tmp/repo", permissionMode: "bypassPermissions" }); + assert.ok(newSessionArgv.includes("--allow-dangerously-skip-permissions")); + fs.writeFileSync( + path.join(home, "settings.json"), + JSON.stringify({ skipDangerousModePermissionPrompt: true }) + ); + pty.spawnRun({ laneId: 4, cwd: "/tmp/repo" }); + } finally { + if (prevHome === undefined) delete process.env.CLAUDE_HOME; + else process.env.CLAUDE_HOME = prevHome; + fs.rmSync(home, { recursive: true, force: true }); + } assert.ok(newSessionArgv.includes("--allow-dangerously-skip-permissions")); // ...as an OPTION only: the starting mode stays the requested one, and // `--dangerously-skip-permissions` (which would enable it) is never passed. diff --git a/server/lib/pty-run.js b/server/lib/pty-run.js index 5d60e9c9..c0601580 100644 --- a/server/lib/pty-run.js +++ b/server/lib/pty-run.js @@ -165,6 +165,24 @@ function promptFileArgv(id, argv) { return ["sh", "-c", READ_PROMPT, "sh", file, ...argv.slice(0, -1)]; } +/** + * Whether the account a pane runs as has accepted Claude Code's bypass + * warning — Claude writes `skipDangerousModePermissionPrompt: true` to its + * user settings once it is. A member's (S3) is read from their home, owner- + * checked; the host's from CLAUDE_HOME. Unreadable reads as "not yet". + */ +function bypassAccepted(member) { + const file = member + ? path.join(member.home, ".claude", "settings.json") + : path.join(require("./claude-home").getClaudeHome(), "settings.json"); + try { + const text = require("./run-as").readFileSafe(file, "utf8", { max: 1 << 20 }); + return JSON.parse(text).skipDangerousModePermissionPrompt === true; + } catch { + return false; + } +} + function buildClaudeArgv({ model, permissionMode, @@ -177,6 +195,7 @@ function buildClaudeArgv({ addDirs = [], systemPromptFile = null, proxyArgv = [], + offerBypass = true, platform = process.platform, }) { const argv = ["claude"]; @@ -210,7 +229,11 @@ function buildClaudeArgv({ // plan/auto/manual/acceptEdits, so a run started in any non-bypass mode could // never reach bypass from inside the pane, while a real terminal could. // The starting mode is still whatever --permission-mode says above. - argv.push("--allow-dangerously-skip-permissions"); + // Only once the account has accepted Claude Code's bypass warning + // (`offerBypass`, see bypassAccepted): before that the flag alone puts the + // warning in front of a brand-new account's first session — one the Chat UI + // cannot answer — although nobody asked for bypass. + if (offerBypass) argv.push("--allow-dangerously-skip-permissions"); if (model) argv.push("--model", model); if (effort && EFFORT_LEVELS.has(effort)) argv.push("--effort", effort); if (resumeSessionId) argv.push("--resume", resumeSessionId); @@ -512,6 +535,11 @@ function spawnRun(args) { resumeSessionId, forkSession: forkSession === true, newSessionId, + // Asking for bypass is consent to see its warning; otherwise only an + // account that already accepted it gets bypass in its shift+tab cycle. + offerBypass: + permissionMode === "bypassPermissions" || + (entry.launcher === "claude" && bypassAccepted(require("./run-as").accountForLane(laneId))), worktreeName: worktree === true ? `ah-${laneId}-${Date.now().toString(36)}` : null, resumeLast, bypass: bypass === true,