Skip to content

fix(cli): prevent root rejection when using canCallTool with bypassPermissions#180

Open
cruzanstx wants to merge 1 commit intotiann:mainfrom
cruzanstx:fix/root-permission-mode-bypass
Open

fix(cli): prevent root rejection when using canCallTool with bypassPermissions#180
cruzanstx wants to merge 1 commit intotiann:mainfrom
cruzanstx:fix/root-permission-mode-bypass

Conversation

@cruzanstx
Copy link

Summary

  • Root cause fix: When running as root, Claude Code rejects --dangerously-skip-permissions (mapped from --permission-mode bypassPermissions). Since canCallTool already handles permission auto-approval via --permission-prompt-tool stdio, skip passing the redundant --permission-mode flag in that case.
  • Abort error fix: Change if/if to if/else if in close handler to prevent exit code error from overwriting AbortError
  • Stderr capture: Always log stderr via logDebug() instead of only when DEBUG env is set — critical for diagnosing silent Claude process exits
  • Remote launcher logging: Log actual error name/message/stack instead of empty {} (Error objects don't JSON.stringify)
  • Sync engine fallback: Fall back to first online machine instead of returning "No machine online" error when no host-specific match is found

Context

When hapi runs as root (common in containerized/server deployments), the --yolo / bypassPermissions mode causes Claude Code to immediately exit with code 1 and the message --dangerously-skip-permissions cannot be used with root/sudo privileges. The remote launcher catches this as an empty error {} and shows "Process exited unexpectedly" to the user.

The fix recognizes that canCallTool + --permission-prompt-tool stdio already achieves the same effect as bypassPermissions — permissions are auto-approved app-side — so the redundant CLI flag can be safely omitted.

Test plan

  • Run hapi as root with --yolo mode and verify Claude sessions start successfully
  • Verify non-root users are unaffected
  • Verify abort (Ctrl-C) still works correctly and doesn't show spurious exit code errors
  • Verify stderr from Claude process appears in hapi debug logs

🤖 Generated with Claude Code

…rmissions

When running as root, Claude Code rejects --dangerously-skip-permissions
(mapped from --permission-mode bypassPermissions). Since canCallTool already
handles permission auto-approval via --permission-prompt-tool stdio, skip
passing the redundant --permission-mode flag in that case.

Also fixes abort error being overwritten by exit code error (else-if),
captures stderr via logDebug for diagnostics, improves error logging in
remote launcher, and falls back to first online machine in sync engine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
if (hostMatch) return hostMatch
}
return null
return onlineMachines[0]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] [Correctness] Resume can target wrong machine

Why this is a problem: When metadata.machineId or metadata.host is set but no match is online, this now falls back to the first online machine. Resume then uses metadata.path on a different machine, risking wrong workspace or unintended session creation.

Suggested fix:

const targetMachine = (() => {
    if (metadata.machineId) {
        const exact = onlineMachines.find((machine) => machine.id === metadata.machineId)
        if (exact) return exact
        return null
    }
    if (metadata.host) {
        const hostMatch = onlineMachines.find((machine) => machine.metadata?.host === metadata.host)
        if (hostMatch) return hostMatch
        return null
    }
    return onlineMachines[0]
})()

if (!targetMachine) {
    return { type: 'error', message: 'No matching machine online', code: 'no_machine_online' }
}

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Resume can target wrong machine when metadata.machineId or metadata.host mismatches; fallback to first online uses metadata.path on another machine. Evidence hub/src/sync/syncEngine.ts:353.
    Suggested fix:
    const targetMachine = (() => {
        if (metadata.machineId) {
            const exact = onlineMachines.find((machine) => machine.id === metadata.machineId)
            if (exact) return exact
            return null
        }
        if (metadata.host) {
            const hostMatch = onlineMachines.find((machine) => machine.metadata?.host === metadata.host)
            if (hostMatch) return hostMatch
            return null
        }
        return onlineMachines[0]
    })()
    
    if (!targetMachine) {
        return { type: 'error', message: 'No matching machine online', code: 'no_machine_online' }
    }

Summary

  • 1 issue. Resume safety risk on multi-machine namespaces.

Testing

  • Not run (automation)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant