-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix: avoid Claude chat exit when bypass mode runs as root #702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # Features | ||
|
|
||
| - Claude chat now uses `dontAsk` instead of `bypassPermissions` when CloudCLI is started as `root` and either skip-permissions or the chat mode button requests bypass mode, preventing Claude SDK chat exits with code 1. | ||
| - Regression coverage is available via `npm run test:claude-sdk-permissions`, which rebuilds `dist-server` before running the permission-mode test. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import test from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
| import { resolveSdkPermissionMode } from '../../../../dist-server/server/claude-sdk.js'; | ||
|
|
||
| // This test validates the compiled server artifact, so run it through | ||
| // `npm run test:claude-sdk-permissions`, which rebuilds dist-server first. | ||
|
Comment on lines
+3
to
+6
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check that CI / workflows reference the new npm script or build before tests.
fd -e yml -e yaml . .github/workflows 2>/dev/null | xargs -I{} sh -c 'echo "=== {} ==="; cat "{}"' | rg -n -C2 'test:claude-sdk-permissions|node --test|build:server'Repository: siteboon/claudecodeui Length of output: 47 🏁 Script executed: # Check if .github/workflows exists and list its contents
ls -la .github/workflows 2>/dev/null || echo "No .github/workflows directory found"Repository: siteboon/claudecodeui Length of output: 367 🏁 Script executed: # List top-level .github directory
ls -la .github 2>/dev/null || echo "No .github directory found"Repository: siteboon/claudecodeui Length of output: 299 🏁 Script executed: # Check package.json for script definitions
head -100 package.json | grep -A 50 '"scripts"'Repository: siteboon/claudecodeui Length of output: 1980 🏁 Script executed: # Look for the specific test file and verify it exists
find . -name "claude-sdk-permission-mode.test.mjs" -type fRepository: siteboon/claudecodeui Length of output: 134 🏁 Script executed: cat .github/workflows/discord-release.ymlRepository: siteboon/claudecodeui Length of output: 728 🏁 Script executed: cat .github/workflows/docker.ymlRepository: siteboon/claudecodeui Length of output: 1570 🏁 Script executed: cat .github/workflows/release.ymlRepository: siteboon/claudecodeui Length of output: 1423 🏁 Script executed: # Look for test configuration files
find . -maxdepth 2 -type f \( -name "jest.config.*" -o -name "vitest.config.*" -o -name "mocha.opts" -o -name ".mocharc.*" \) 2>/dev/nullRepository: siteboon/claudecodeui Length of output: 47 🏁 Script executed: # Check if there are any other test files
find server/modules/providers/tests -type f -name "*.test.*" -o -name "*.spec.*" 2>/dev/null | head -20Repository: siteboon/claudecodeui Length of output: 175 🏁 Script executed: # Search for "test" references in package.json scripts
grep -A 100 '"scripts"' package.json | grep -i testRepository: siteboon/claudecodeui Length of output: 205 CI workflows don't invoke the test:claude-sdk-permissions script yet. The script is correctly defined in 🤖 Prompt for AI Agents |
||
|
|
||
| test('maps skipPermissions to dontAsk for root Claude SDK sessions', () => { | ||
| assert.equal(resolveSdkPermissionMode('default', { skipPermissions: true }, { isRoot: true }), 'dontAsk'); | ||
| }); | ||
|
|
||
| test('maps skipPermissions to bypassPermissions for non-root Claude SDK sessions', () => { | ||
| assert.equal(resolveSdkPermissionMode('default', { skipPermissions: true }, { isRoot: false }), 'bypassPermissions'); | ||
| }); | ||
|
|
||
| test('preserves plan mode when skipPermissions is enabled', () => { | ||
| assert.equal(resolveSdkPermissionMode('plan', { skipPermissions: true }, { isRoot: true }), 'plan'); | ||
| }); | ||
|
|
||
| test('preserves explicit non-default permission modes when skipPermissions is disabled', () => { | ||
| assert.equal(resolveSdkPermissionMode('acceptEdits', { skipPermissions: false }, { isRoot: true }), 'acceptEdits'); | ||
| }); | ||
|
|
||
| test('maps explicit bypassPermissions to dontAsk for root Claude SDK sessions', () => { | ||
| assert.equal(resolveSdkPermissionMode('bypassPermissions', { skipPermissions: false }, { isRoot: true }), 'dontAsk'); | ||
| }); | ||
|
|
||
| test('preserves explicit bypassPermissions for non-root Claude SDK sessions', () => { | ||
| assert.equal(resolveSdkPermissionMode('bypassPermissions', { skipPermissions: false }, { isRoot: false }), 'bypassPermissions'); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: siteboon/claudecodeui
Length of output: 2786
🌐 Web query:
Claude Agent SDK permissionMode dontAsk canUseTool callback called💡 Result:
In the Claude Agent SDK, when permissionMode is set to "dontAsk", the canUseTool callback is not called. Instead, tools that are not pre-approved by allowed_tools, hooks, or permission rules are automatically denied without prompting or invoking the callback. This creates a locked-down agent that only uses explicitly permitted tools, ideal for headless or secure environments.
Citations:
auto(anddontAsk) permission modes from @anthropic-ai/claude-agent-sdk slopus/happy#1156🏁 Script executed:
Repository: siteboon/claudecodeui
Length of output: 3240
🏁 Script executed:
Repository: siteboon/claudecodeui
Length of output: 646
🏁 Script executed:
Repository: siteboon/claudecodeui
Length of output: 47
🏁 Script executed:
Repository: siteboon/claudecodeui
Length of output: 1878
🏁 Script executed:
Repository: siteboon/claudecodeui
Length of output: 2668
Fix:
dontAsksemantics aren't equivalent tobypassPermissionsand silently denies tools under root.Per the Claude Agent SDK documentation,
dontAskdenies any tool not pre-approved byallowedTools/settings/hooks; thecanUseToolcallback is never invoked at the permission-check step. This creates a critical mismatch in the current code:The problem:
mapCliOptionsToSDKcallsresolveSdkPermissionMode, which transformsbypassPermissions→dontAskwhen running as root (line 45-49).server/routes/agent.js:950–956andserver/routes/agent.js:974–977pass onlypermissionMode: 'bypassPermissions'with notoolsSettingsorallowedTools.dontAsk,toolsSettingsdefaults to{allowedTools: []}(empty list, line 186–190).canUseToolcallback at line 546 checks for the literal string'bypassPermissions', but sincepermissionModeis now'dontAsk', the early-allow branch never fires.The same issue affects
server/routes/git.js:983–987and potentially other root-run scenarios.Minimum required fixes:
bypassPermissions→dontAsktransformation, orallowedToolswith the actual set of tools the agent may need before callingqueryClaudeSDK, orcanUseToolto also recognize'dontAsk'mode and apply bypass logic (though this inverts the semantic intent ofdontAsk).Document the intended root-user behavior (if silent tool denial is desired, clarify that in the permission mode semantics; if bypass is intended, fix the transformation).