Let Claude Code run sudo commands on Linux — with a graphical password
prompt and a time‑limited password cache — even though Claude's shell has no
terminal.
TL;DR: a PreToolUse hook rewrites
sudo …→SUDO_ASKPASS=… sudo -A …so sudo authenticates through a GUI dialog (zenity/kdialog/ssh-askpass) instead of a tty, and the password is cached for ~1h so you aren't prompted every time.
Claude Code executes Bash tool commands in a shell with no controlling tty
(tty prints not a tty). That breaks sudo in two ways:
- sudo can't ask for a password. With no tty,
sudoerrors withsudo: a terminal is required to authenticate(or hangs). - Caching credentials elsewhere doesn't help. Modern Ubuntu (25.04+)
ships
sudo-rs(a Rust reimplementation) as the defaultsudo. Its credential "tickets" are keyed per‑tty/per‑process, and:- it has no
timestamp_type=globaloption (verified insudoers-rs(5)), so you cannot make tickets span sessions; - a
sudo -vyou run in another terminal is keyed to that terminal and is invisible to Claude's tty‑less context; - even a ticket cached by the hook process is not shared with the command's process (both are separate, tty‑less children) — verified empirically.
- it has no
A common "fix" is a hook that blocks sudo and tells you to run sudo -v in
another terminal. On sudo-rs that can never succeed — the cached ticket
is never visible to Claude. This package fixes the root cause instead.
sudo-rs does support -A / SUDO_ASKPASS (a password helper program that
needs no tty). The pieces:
┌─ Claude runs: sudo update-grub
│
├─ PreToolUse hook (sudo-check.sh) intercepts the Bash command and rewrites it
│ via Claude Code's `updatedInput` to:
│ export SUDO_ASKPASS=~/.claude/hooks/askpass.sh; sudo -A update-grub
│
├─ sudo -A runs askpass.sh to get the password (no tty needed)
│
└─ askpass.sh:
• fresh cache? -> prints cached password silently
• otherwise -> pops a zenity/kdialog dialog, caches the password
Two scripts:
| file | role |
|---|---|
hooks/sudo-check.sh |
PreToolUse hook (matcher Bash). Rewrites any sudo command to use askpass. |
hooks/askpass.sh |
SUDO_ASKPASS helper. Graphical prompt + sliding 1h password cache. |
A Claude Code PreToolUse hook can rewrite the tool input by printing this to stdout (exit 0):
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": { "command": "export SUDO_ASKPASS=...; sudo -A update-grub" }
}
}updatedInput replaces only the fields you include. (Top‑level updatedInput
is not honored — it must be nested under hookSpecificOutput.)
- OS: Linux with a graphical session (
DISPLAYorWAYLAND_DISPLAYset). jqonPATH—sudo apt install jq.- An askpass dialog — one of
zenity(GNOME),kdialog(KDE), orssh-askpass. zenity is used in the original setup. - Works with both
sudo-rsand classicsudo(both support-A).
./install.shThis copies the scripts to ~/.claude/hooks/ and adds a PreToolUse hook to
~/.claude/settings.json (backing it up to settings.json.bak). It is
idempotent. Restart Claude Code (or start a new session) so it reloads
settings.json.
-
Copy
hooks/askpass.shandhooks/sudo-check.shto~/.claude/hooks/andchmod +xboth. -
Merge this into
~/.claude/settings.json:{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "$HOME/.claude/hooks/sudo-check.sh" } ] } ] } } -
Restart Claude Code.
Ask Claude Code to run:
sudo idExpected: one graphical password prompt the first time, then
uid=0(root) …. Subsequent sudo commands run without a prompt until the
cache goes idle.
Check the cache exists and is private:
ls -l "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/claude-sudo.cache" # -rw------- you| Setting | How | Default |
|---|---|---|
| Cache lifetime (idle) | CLAUDE_SUDO_TTL env var, in seconds |
3600 (1h) |
| Disable caching (prompt every time) | CLAUDE_SUDO_TTL=0 |
— |
| Clear cache now | rm -f "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/claude-sudo.cache" |
— |
The cache window is sliding: every sudo use refreshes it, so it only
re‑prompts after CLAUDE_SUDO_TTL seconds of no sudo activity.
- The password is cached in plaintext in tmpfs
(
$XDG_RUNTIME_DIR/claude-sudo.cache, mode0600). tmpfs is RAM‑backed, user‑only, and wiped on reboot/logout. - Anything able to read your processes/files as your user could read it
during the cache window. If that is unacceptable, set a short
CLAUDE_SUDO_TTL, useCLAUDE_SUDO_TTL=0, or clear the cache when done. - This grants Claude Code the ability to obtain root via your password. That is
the point — make sure it matches your intent. To revoke, run
./uninstall.sh. - This approach keeps password protection (no
NOPASSWDsudoers changes required). ANOPASSWDrule is an alternative but removes the password gate entirely.
| Symptom | Cause / fix |
|---|---|
sudo: a terminal is required to authenticate |
Hook not applied. Restart Claude Code so settings.json reloads; confirm the hook path in settings is correct and sudo-check.sh is executable. |
| Command runs unmodified (no rewrite) | updatedInput must be nested under hookSpecificOutput (see above). Older Claude Code may lack updatedInput support — update Claude Code. |
No dialog appears / no graphical askpass/display deny message |
No DISPLAY/WAYLAND_DISPLAY, or no zenity/kdialog/ssh-askpass installed. Install one (sudo apt install zenity). |
| Wrong password cached after you changed it | rm -f "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/claude-sudo.cache" and retry. |
| Hook seems to block its own diagnostics | The hook only acts on commands containing the word sudo; that's expected. |
The hook matches the Bash tool. It inspects .tool_input.command; if the
command contains the standalone word sudo (and isn't already wired for
askpass), it rewrites it. All other commands pass through untouched (exit 0,
no output).
./uninstall.shRemoves the hook registration from settings.json, deletes the installed
scripts, and clears the cached password. Restart Claude Code afterwards.
claude-code-root-hook/
├── README.md
├── install.sh # copy scripts + register hook (idempotent)
├── uninstall.sh # remove hook + scripts + cache
└── hooks/
├── sudo-check.sh # PreToolUse Bash hook: rewrites sudo -> sudo -A
└── askpass.sh # SUDO_ASKPASS helper: GUI prompt + 1h cache