pass-cli session bootstrap: one shared helper, and make it self-heal - #89
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
The inline code read the
infofailure as "log in", the login refused, andset -ekilled the script. In a deploy script that aborts beforecompose 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-livesynchad already been removed, the deploy died here, and the service stayed down until/tmp/pass-agent-selfhosted/.sessionwas cleared by hand.The fix
pass_session_ensuredetects 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 intorm -rf /.session.info, never by login's exit status. That's the whole lesson of the bug.Why all nine scripts, not just
pass-deploy.shThe 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
infoconfirms livepass-clithat failsinfoand refusesloginwhile a stale.sessionexists — detected, cleared, retried, exit 0, directory gonePlus the real consumers end to end:
pass-deploy.shredeployedobsidian-livesync(healthy), anddns-nextdns.shlisted its 13 rewrites. All nine scripts passsh -n.🤖 Generated with Claude Code