call_api was swallowing every byte of oauth.sh ensure's stderr with
`2>/dev/null`, so when ensure returned an empty token there was zero
diagnostic info — just "OAuth token unavailable". With Bryan hitting an
intermittent failure on MobaXterm we'd already burned two guess-fix
cycles; this ships the data instead of another guess.
Changes:
- call_api now captures ensure's stderr to a tempfile and surfaces it
via err() when the token comes back empty, pointing the user at
/oauth-debug for full state.
- cmd_ensure validates the file parses as JSON before destructuring,
validates .access_token is non-empty before emitting, and emits a
decision trace to stderr under LARRY_OAUTH_DEBUG=1.
- New cmd_debug subcommand (oauth.sh debug) dumps: file state (mode,
size, mtime, JSON validity), parsed fetched_at + expires_in + now +
computed expiry + would_refresh decision, jq binary path + version +
Unix/Windows-native flavor, cygpath -w translation when on Cygwin,
truncated previews of access/refresh tokens (first 20 chars + length
only — safe to share), and a live LARRY_OAUTH_DEBUG=1 ensure trace.
- New /oauth-debug slash command exposes it from the REPL, documented
in /help.
- cmd_login and cmd_refresh now write to .new sidecars, validate
required keys parse, then atomically mv — guards against the
corrupted-file failure mode that would silently break ensure on a
later run.
Happy path unchanged: when the file is valid and the token is in-window
ensure prints just the access_token on stdout with no stderr.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>