The handoff watcher

A local daemon that runs TTY-bound commands in your shell so Claude never has to stop and ask.

Some commands cannot run in an agent’s non-interactive Bash. Without a mechanism, Claude has to stop and ask you to paste ! cmd, which breaks both its flow and your concentration.

devflow-watch is a small local daemon that closes that loop.

Quick start

devflow-watch start      # start the daemon for the current project
devflow-watch status     # verify
devflow-watch stop

That is the whole setup. From then on, when Claude tries to run something on the allowlist, the gate-interactive hook queues it instead of failing, the daemon executes it in your interactive shell, and the route-results hook injects the result into Claude’s next turn.

What gets intercepted

TTY-interactive auth flows — cloud and registry CLIs whose login is a browser or prompt flow: DigitalOcean, GitHub, Google Cloud, AWS (including SSO), 1Password, npm, Vault.

Password and passphrase promptspasswd, ssh-keygen without -N "".

Shell-flow tools that depend on aliases, sourced rc files or shimmed shell sessions — nvm use, nvm install, pyenv shell, pyenv install, conda activate, direnv exec, direnv allow, mise use, mise install, mise run, asdf shell, asdf install, rbenv shell.

How it flows

Claude runs a gated command
gate-interactive denies the Bash call
  → writes .devflow-handoff/pending/<id>.json
  → tells Claude "queued for daemon — continue with other work"
devflow-watch picks it up, runs it in your interactive shell
  → writes .devflow-handoff/done/<id>.json
route-results injects the result on your next prompt
  → Claude resumes without you pasting anything

Without the daemon

Everything still works, just with one manual step. gate-interactive prints the command for you to run with ! cmd, and Claude picks up the output inline. The value even in this mode is that Claude recognises which commands need a shell and does not waste a turn discovering it by failing.

Configuration

{
  "daemon": {
    "notifications": false,
    "notify_on_start": true,
    "notify_on_complete": true,
    "auto_launch": false,
    "multi_project": false,
    "cross_shell": [],
    "status_line": false
  }
}
VariableEffect
DEVFLOW_WATCH_ALLOW_FILEPath to a custom allowlist
DEVFLOW_HANDOFF_PID_FILEOverride the PID file location
DEVFLOW_HANDOFF_RESULT_TTL_MSHow long an unconsumed result stays valid
DEVFLOW_SKIP_INTERACTIVE_GATE=1Disable interception entirely

Allowlist and deny list

The allowlist is curated rather than open — the daemon runs commands in your shell, so what it will accept is a security boundary, not a convenience setting. Extend it with your own patterns via DEVFLOW_WATCH_ALLOW_FILE. A deny list takes precedence over the allowlist.

Auto-launch

The daemon can install itself as a launchd (macOS) or systemd (Linux) unit so it starts with your session:

devflow-watch install
devflow-watch uninstall

Multi-project

One daemon can watch several projects, added and removed while it runs. devflow-watch status shows all watched projects with per-project pending and done counts.

A known gap

gate-interactive matches against the raw command string, so a Bash call whose payload mentions a gated command — a heredoc writing documentation about aws sso login, say — can be intercepted even though nothing interactive would run.

gate-commits solved this class of problem with invocation-aware detection that strips heredoc bodies and quoted arguments before matching. gate-interactive does not yet do the same. If you hit it, the escape hatch is DEVFLOW_SKIP_INTERACTIVE_GATE=1.