Use this when:
- A command will prompt for credentials, a token, a passphrase, or open a browser flow
- A command depends on shell-init state Claude's sub-shell doesn't have (nvm, mise, conda, direnv, pyenv, asdf, rbenv)
- A previous Bash attempt was denied by
gate-interactive.js(PreToolUse hook) - You want to pre-empt the friction before the harness fights an interactive prompt
Out of scope: commands that can run non-interactively via flags or env vars (e.g. gh auth login --with-token < tokenfile). Prefer the non-interactive form when available.
Triggers
Triggers on: "auth init", "log in to", "interactive command", "needs my password", "I need to paste a token", "needs my shell environment", "nvm/mise/conda activate".
When the devflow-watch daemon is running, the handoff is fully non-disruptive — Claude continues executing while the daemon runs the command and returns the result on the next turn.
node ~/.claude/devflow/bin/devflow-watch.cjs status
The status JSON has a running: true|false field. Branch on that for the next steps.
- If
running: true→ Approach B (non-disruptive): write pending record, continue with other work, expect result on next turn. - If
running: false→ Approach A (paste-driven): write pending record AND instruct user to paste! cmd.
node ~/.claude/devflow/bin/df-tools.cjs handoff create "$ARGUMENTS"
The command returns JSON with {id, path, record}. Capture the id.
If df-tools is unavailable, fall back to writing the record manually:
mkdir -p .devflow-handoff/pending
id="h-$(date +%s)-$RANDOM"
cat > ".devflow-handoff/pending/${id}.json" <<EOF
{"id":"${id}","cmd":"$ARGUMENTS","cwd":"$(pwd)","status":"pending","source":"hook","created_at":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
EOF
echo "$id"
Print the user-facing instruction in a single short message. Use this exact shape so the user can copy with one click:
I need to run this in your shell. Please paste:
! $ARGUMENTS
(The `!` prefix runs it in your shell — output returns inline and I'll continue from there. Tip: run `devflow-watch start` once to skip this paste step in future.)
Do not retry the command via the Bash tool. Wait for the user's next message containing the command's output.
The daemon will pick up the pending record from .devflow-handoff/pending/<id>.json, run it in the user's interactive shell, write the result to .devflow-handoff/done/<id>.json, and the route-results.js UserPromptSubmit hook will inject the result into your next turn as additionalContext.
What you should do RIGHT NOW:
- Acknowledge in one line that the command was queued: e.g.
"Queued \gh auth login` to the watcher (handoff id: h-abc123). Continuing with other work."` - Do NOT instruct the user to paste anything.
- Do NOT retry the Bash tool for this command.
- Continue with any non-blocking work you can do without the result.
- On the user's next turn, the result will appear automatically as
additionalContextfrom the route-results hook. Pick up whatever depended on this command at that point.
- Approach B (most common when watcher is running): the result arrives as
additionalContextfrom the route-results hook, marked under## Deferred command results. Read the stdout/stderr/exit_code, validate success, and continue the deferred work. - Approach A (fallback): the result is in the user's message body (whatever the harness echoed back from
! cmd). Same continuation logic.
In either case:
- Read the output as if you had run the command yourself
- Continue with whatever follow-on work was queued (e.g. for
doctl auth init, the next step would bedoctl account getordoctl apps list) - If the command failed (
exit_code != 0) or was cancelled, ask the user what they'd like to do — do not silently retry - If the daemon rejected the command (status: rejected), do NOT retry — the allowlist excluded it. Ask the user to either run it manually or extend the allowlist.