feat(acp): bypassPermissions 默认显示,去掉 opt-in 限制

之前 bypassPermissions 需要本地显式 opt-in(ACP_PERMISSION_MODE 环境变量、
CLAUDE_CODE_ACP_ALLOW_BYPASS_PERMISSIONS 环境变量、或 settings.permissions.defaultMode)
才会出现在 modes 列表里 —— 标准客户端看不到这个 mode,永远没法切换。

去掉 opt-in 后,只要进程级允许(非 root 或 IS_SANDBOX=1)就显示。
- permissionMode: isAcpBypassPermissionModeAvailable 只保留进程级检查,删除
  isAcpBypassLocallyEnabled / isSettingsBypassPermissionMode / isTruthyEnv 等
  只服务于 opt-in 的辅助函数
- createSessionMethod: 调用方去掉 settingsMode 参数
- agent.test: 反转所有依赖 "bypass 需要 opt-in" 的断言

Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
claude-code-best
2026-06-20 10:53:49 +08:00
parent 704c6c7814
commit 0f2eec496c
3 changed files with 43 additions and 72 deletions

View File

@@ -137,10 +137,10 @@ async function createSession(
// Parse MCP servers from ACP params
// MCP server config is handled separately in the tools system
// ACP clients can expose bypass only when both the process and local config allow it.
const isBypassAvailable = isAcpBypassPermissionModeAvailable(
settingsPermissionMode,
)
// bypassPermissions is exposed to ACP clients whenever the process itself allows it
// (non-root or sandbox). The previous additional opt-in gate made the mode invisible
// to standard clients and defeated the purpose of listing it. See permissionMode.ts.
const isBypassAvailable = isAcpBypassPermissionModeAvailable()
// Create a mutable AppState for the session
const appState: AppState = {

View File

@@ -26,10 +26,10 @@ export function resolveSessionPermissionMode(
)
if (
metaResolved === 'bypassPermissions' &&
!isAcpBypassPermissionModeAvailable(settingsMode)
!isAcpBypassPermissionModeAvailable()
) {
throw new Error(
'Mode not available: bypassPermissions requires a local ACP bypass opt-in.',
'Mode not available: bypassPermissions cannot run as root (start the agent as a non-root user, or set IS_SANDBOX=1).',
)
}
@@ -78,14 +78,20 @@ export function hasOwnField(
return !!value && Object.hasOwn(value, key)
}
export function isAcpBypassPermissionModeAvailable(
settingsMode?: unknown,
): boolean {
return (
isProcessBypassPermissionModeAvailable() &&
(isAcpBypassLocallyEnabled() ||
isSettingsBypassPermissionMode(settingsMode))
)
/**
* Whether bypassPermissions is selectable by ACP clients.
*
* The previous implementation required a local opt-in (ACP_PERMISSION_MODE env var,
* CLAUDE_CODE_ACP_ALLOW_BYPASS_PERMISSIONS env var, or settings.permissions.defaultMode).
* That gate made the mode invisible to standard clients unless the operator already
* pre-configured it — defeating the point of exposing it through the ACP mode list.
*
* The only remaining guard is the process-level one: bypass must not silently run
* as root (where every skipped permission check is a privilege boundary crossed),
* unless explicitly marked as a sandbox.
*/
export function isAcpBypassPermissionModeAvailable(): boolean {
return isProcessBypassPermissionModeAvailable()
}
function isProcessBypassPermissionModeAvailable(): boolean {
@@ -94,22 +100,3 @@ function isProcessBypassPermissionModeAvailable(): boolean {
if (typeof process.getuid === 'function') return process.getuid() !== 0
return true
}
function isAcpBypassLocallyEnabled(): boolean {
return (
process.env.ACP_PERMISSION_MODE === 'bypassPermissions' ||
isTruthyEnv(process.env.CLAUDE_CODE_ACP_ALLOW_BYPASS_PERMISSIONS)
)
}
function isSettingsBypassPermissionMode(settingsMode: unknown): boolean {
try {
return resolvePermissionMode(settingsMode) === 'bypassPermissions'
} catch {
return false
}
}
function isTruthyEnv(value: string | undefined): boolean {
return value === '1' || value?.toLowerCase() === 'true'
}