Skip to content

pass-cli session bootstrap: one shared helper, and make it self-heal - #89

Merged
mathewcsims merged 1 commit into
mainfrom
pass-session-self-heal
Aug 14, 2026
Merged

pass-cli session bootstrap: one shared helper, and make it self-heal#89
mathewcsims merged 1 commit into
mainfrom
pass-session-self-heal

Conversation

@mathewcsims

Copy link
Copy Markdown
Owner

A corrupt pass-cli session took an app down today. Fixing it in one script would have left eight others carrying the same bug, so the login block is now a single function.

The bug

A pass-cli session can be corrupt rather than absent — the directory exists but the session is invalid server-side — and the two commands then contradict each other:

pass-cli info   -> "failed to authenticate: non-existent session"  (fails)
pass-cli login  -> "Error: Already authenticated"                  (fails)

The inline code read the info failure as "log in", the login refused, and set -e killed the script. In a deploy script that aborts before compose up — so a container removed ready for recreation just stays down, with a contradictory message as the only clue.

Hit for real today, mid-way through rotating the Obsidian LiveSync CouchDB admin password: obsidian-livesync had already been removed, the deploy died here, and the service stayed down until /tmp/pass-agent-selfhosted/.session was cleared by hand.

The fix

pass_session_ensure detects exactly that disagreement, clears the stale session directory, and retries once.

  • rm -rf "${PROTON_PASS_SESSION_DIR:?}/…":? so an unset variable can never turn that into rm -rf /.session.
  • Success is judged by info, never by login's exit status. That's the whole lesson of the bug.
  • login's output is captured rather than discarded — it was the only thing distinguishing a corrupt session from a revoked token.

Why all nine scripts, not just pass-deploy.sh

The block was duplicated across nine scripts in two variants that had already drifted: four verified the login afterwards, five did not. Nine copies of a fix is how they drift again. The five weaker ones gain the post-login verification as a side effect.

Converted: dns-digitalocean, dns-nextdns, mirror-backup-to-external-drive, pass-deploy, pass-deploy-remote, pass-deploy-kopia-server, pass-render-file, pass-seed-apprise, tailscale-acl.

This is wider than the reported fault, deliberately — flagging it rather than burying it.

Tested, all three paths

Path Result
Valid session present No-op, returns 0
No session at all Logs in; info confirms live
Corrupt session Reproduced against a stubbed pass-cli that fails info and refuses login while a stale .session exists — detected, cleared, retried, exit 0, directory gone

Plus the real consumers end to end: pass-deploy.sh redeployed obsidian-livesync (healthy), and dns-nextdns.sh listed its 13 rewrites. All nine scripts pass sh -n.

🤖 Generated with Claude Code

A corrupt pass-cli session took an app down today. Fixing that in one script
would have left eight others carrying the same bug, so the login block is now
a single function.

THE BUG. A session can be corrupt rather than absent: the directory exists
but the session is invalid server-side, and the two commands then contradict
each other —

    pass-cli info   -> "failed to authenticate: non-existent session"  (fails)
    pass-cli login  -> "Error: Already authenticated"                  (fails)

The inline code read the `info` failure as "log in", the login refused, and
`set -e` killed the script. In a deploy script that aborts BEFORE
`compose up`, so a container removed ready for recreation stays down — with a
contradictory message as the only clue.

Hit for real on 2026-08-14 mid-way through rotating the Obsidian LiveSync
CouchDB admin password: obsidian-livesync had already been removed, the
deploy died here, and the service stayed down until
/tmp/pass-agent-selfhosted/.session was cleared by hand.

pass_session_ensure now detects exactly that disagreement, clears the stale
session directory and retries once. `rm -rf "${PROTON_PASS_SESSION_DIR:?}/..."`
so an unset variable can never make that a catastrophic path. Success is
judged by `info`, never by login's exit status — the whole lesson of the bug.
login's output is captured rather than discarded, since it was the only thing
distinguishing a corrupt session from a revoked token.

WHY ALL NINE, not just pass-deploy.sh. The block was duplicated across nine
scripts in two variants that had already drifted: four verified the login
afterwards, five did not. Nine copies of a fix is how they drift again. The
five weaker ones gain the post-login verification as a side effect.

Converted: dns-digitalocean, dns-nextdns, mirror-backup-to-external-drive,
pass-deploy, pass-deploy-remote, pass-deploy-kopia-server, pass-render-file,
pass-seed-apprise, tailscale-acl.

TESTED, all three paths:
  * valid session present        -> no-op, returns 0
  * no session at all            -> logs in, `info` confirms live
  * corrupt session              -> reproduced against a stubbed pass-cli
    that fails `info` and refuses `login` while a stale .session exists.
    Detected, cleared, retried, exit 0, directory gone.
Plus the real consumers end-to-end: pass-deploy.sh redeployed
obsidian-livesync (healthy), and dns-nextdns.sh listed its 13 rewrites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mathewcsims
mathewcsims merged commit 8e9469f into main Aug 14, 2026
7 checks passed
@mathewcsims
mathewcsims deleted the pass-session-self-heal branch August 14, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant