bitwarden / bitwarden/agent-access

Feature request: --no-tui flag / AAC_HEADLESS env for headless daemon use (ratatui panic under macOS LaunchAgent Background)

Open
#147 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
145
Forks
11
Avg merge
20h 57m
Merged PRs (30d)
1

Description

## Summary

`aac listen` panics at startup when invoked without a controlling terminal (e.g., under macOS LaunchAgent `ProcessType: Background`, systemd headless daemon, container with no PTY, `nohup` invocation, etc.). Would like a `--no-tui` flag (or `AAC_HEADLESS=1` env var) that skips the ratatui TUI init entirely and runs the listener as a pure background daemon with plain-text logging to stdout/stderr.

## Version

```
$ aac --version
aac 0.11.0 (3b000d15af71be5ddcf8893b099f3a66d386be9d)
```

## Use case

Running `aac listen` as an always-on background service on macOS via LaunchAgent, so daemons that need Bitwarden-injected credentials (via `aac run --domain X --env VAR=field -- `) can fetch reliably across reboots + user-session transitions without depending on a foreground Terminal window.

Current workaround: wrap `aac listen` in `/usr/bin/script -q /dev/null ` — `script(1)` allocates a full PTY that satisfies ratatui's `Terminal::init()`. Works but adds a wrapper layer + prevents attach-for-approval (approval prompts happen inside the script-owned PTY which isn't reattachable). A `--no-tui` flag would eliminate the wrapper + let approval prompts either (a) route to an out-of-band notification channel per #142 OR (b) skip approval when `--reusable-psk` cache hit exists.

## Reproduction

Minimal LaunchAgent plist (`~/Library/LaunchAgents/com.example.aac_listen.plist`):

```xml

Label
com.example.aac_listen
Program
/opt/homebrew/bin/aac
ProgramArguments

/opt/homebrew/bin/aac
listen
--reusable-psk
--provider
bitwarden
-v

RunAtLoad
StandardOutPath/tmp/aac.log
StandardErrorPath/tmp/aac.err
EnvironmentVariables

PATH/opt/homebrew/bin:/usr/bin:/bin
BW_SESSION

ProcessTypeBackground

```

`launchctl load ~/Library/LaunchAgents/com.example.aac_listen.plist` → immediate panic + KeepAlive respawn loop.

## Panic output (`/tmp/aac.err`)

```
The application panicked (crashed).
Message: failed to initialize terminal: Os { code: 6, kind: Uncategorized, message: "Device not configured" }
Location: /Users/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/ratatui-0.29.0/src/terminal/init.rs:52

Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
```

`Os code: 6` = macOS `ENXIO` returned when `open("/dev/tty", ...)` fails because the process has no controlling terminal. ratatui `Terminal::init()` opens `/dev/tty` unconditionally regardless of flag combination — `--reusable-psk` (which pre-caches PSK to disk) doesn't bypass the TUI init because init happens before flag processing that would enable headless mode.

## Requested change

Add one of:

1. `--no-tui` flag → skip ratatui `Terminal::init()`; log approval state + errors as structured stderr text; approval requests emit to stderr with an out-of-band callback URL/channel (composes cleanly with #142 out-of-band notification feature request)
2. `AAC_HEADLESS=1` env var equivalent
3. Auto-detect: if `isatty(0)` and `isatty(1)` and `isatty(2)` are all false → skip TUI init automatically (Unix convention)

Bonus: with `--no-tui`, `--reusable-psk` cache hits could auto-approve without any user interaction (since the whole point of PSK cache is "this remote client was previously approved for this domain"). This would enable fully-headless daemon lifecycle.

## Empirical validation of workaround (for anyone hitting this)

```bash
# Works — script(1) allocates PTY that satisfies ratatui:
exec /usr/bin/script -q /dev/null /opt/homebrew/bin/aac listen --reusable-psk --provider bitwarden -v

# Fails — screen -dmS silent-dies under launchd Background context:
/usr/bin/screen -dmS aactest '/opt/homebrew/bin/aac listen ...'

# Fails — bare invocation under LaunchAgent Background = ratatui panic:
Program = /opt/homebrew/bin/aac; ProgramArguments = [aac, listen, ...]
```

## Related

- #139 approval prompts desync (would matter less with headless mode + out-of-band approval channel)
- #142 out-of-band notification for pending approvals (composes with `--no-tui`)

Contributor guide

Open the contributing guide

Research direction

Start at the `aac listen` startup path and the ratatui `Terminal::init()` call described in the panic. Compare the requested `--no-tui`, `AAC_HEADLESS=1`, and automatic detection options, then define approval and logging behavior for headless operation. Done means a no-PTY invocation avoids the panic and reports errors and approval state through the documented non-TUI channel.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.