Stop transient boot-time probe failures from permanently parking Signal needs_reauth - #170
Merged
Merged
Conversation
…al needs_reauth Three live episodes (2026-07-20, 2026-07-24, 2026-08-06) parked a valid Signal link in needs_reauth for 12-22h because one boot-time listAccounts came up empty — signal-cli racing its own account bootstrap exits 0 with zero accounts — and the receive-start probe classified that single local read as terminal reauth. A manual POST /api/signal/connect reconnected in ~5s each time, proving the park was interpretation, not server truth. The probe path now treats listAccounts as the local-only evidence it is: - Retry in-generation: 3 paced attempts (each bounded to 30s, previously unbounded) before any classification. - Corroborate before parking: an exhausted empty/invalid probe parks immediately only when accounts.json also lists no account (locally gone, unchanged semantics). While accounts.json still lists the link, the generation exits transient (signal_account_probe_empty) and only 3 consecutive disagreeing generations park, under the new signal_account_unreadable fingerprint. - Self-retest the local-evidence park: the cmd supervisor control retries a signal_account_unreadable park every 15 minutes via RetryBlocked (a local probe), bounding a lingering false park to minutes instead of a manual-connect-or-nothing forever state. Server-backed evidence keeps parking and stays parked: receive-loop "not registered"/"authorization failed" now needs 2 consecutive confirmations (still seconds for a genuine unlink), parks under signal_account_invalid, and is never retried automatically. Nothing auto-unpairs — unpair still deletes signal-cli state including CDN-expired media and remains manual-only. Tests cover the healed boot race, streak escalation and its reset, receive-glitch immunity, consecutive-confirmation parking, and that the park retest fires only for signal_account_unreadable. Runbook documents the fingerprints and the debug flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mwhobrey
pushed a commit
to mwhobrey/om-tui
that referenced
this pull request
Aug 24, 2026
…al needs_reauth (MaxGhenis#170) Three live episodes (2026-07-20, 2026-07-24, 2026-08-06) parked a valid Signal link in needs_reauth for 12-22h because one boot-time listAccounts came up empty — signal-cli racing its own account bootstrap exits 0 with zero accounts — and the receive-start probe classified that single local read as terminal reauth. A manual POST /api/signal/connect reconnected in ~5s each time, proving the park was interpretation, not server truth. The probe path now treats listAccounts as the local-only evidence it is: - Retry in-generation: 3 paced attempts (each bounded to 30s, previously unbounded) before any classification. - Corroborate before parking: an exhausted empty/invalid probe parks immediately only when accounts.json also lists no account (locally gone, unchanged semantics). While accounts.json still lists the link, the generation exits transient (signal_account_probe_empty) and only 3 consecutive disagreeing generations park, under the new signal_account_unreadable fingerprint. - Self-retest the local-evidence park: the cmd supervisor control retries a signal_account_unreadable park every 15 minutes via RetryBlocked (a local probe), bounding a lingering false park to minutes instead of a manual-connect-or-nothing forever state. Server-backed evidence keeps parking and stays parked: receive-loop "not registered"/"authorization failed" now needs 2 consecutive confirmations (still seconds for a genuine unlink), parks under signal_account_invalid, and is never retried automatically. Nothing auto-unpairs — unpair still deletes signal-cli state including CDN-expired media and remains manual-only. Tests cover the healed boot race, streak escalation and its reset, receive-glitch immunity, consecutive-confirmation parking, and that the park retest fires only for signal_account_unreadable. Runbook documents the fingerprints and the debug flow. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit 39576f3)
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.
Problem
The Signal bridge recurrently latched a permanent
needs_reauthpark from a transient boot-time failure on a link that was actually valid. Three live episodes on the production install:lsofon the config dir empty). The park held 12–22h with zero receipts whileaccounts.jsonwas untouched since April; a singlePOST /api/signal/connectreconnected in ~5 seconds.Root cause:
runReceiveLoop's startup probe ran onelistAccountsand treated an empty success (err == nil && account == "") as terminal proof of an unlinked account (last_error: "Signal account is not paired",needs_reauth: true→PollerFailureReauth→ supervisorStateBlocked, forever). ButlistAccountsis local-only evidence: signal-cli racing its own account bootstrap at backend boot logsIgnoring <number>: User is not registered.and exits 0 with zero accounts — the exact signature the existingTestParseSignalAccountsIgnoresSignalCLIErrorOutputfixture documents.Fix (all three prongs from the incident analysis)
(a) Retry before classifying — the receive-start probe now makes 3 paced in-generation attempts (each bounded to 30s; previously a single unbounded call) before any terminal classification. The observed boot race heals on attempt 2 without even churning a generation.
(b) Verify absence before demanding re-pair — an exhausted empty/invalid probe parks reauth immediately only when
data/accounts.jsonalso lists no account (locally gone — unchanged semantics, samesignal_account_invalidfingerprint). Whileaccounts.jsonstill lists the link — both live false-park episodes — the generation exits transient (signal_account_probe_empty, retried on supervisor backoff), and only 3 consecutive disagreeing generations park, under a new distinct fingerprintsignal_account_unreadable.(c) The local-evidence park self-retests —
signalSupervisorControl.StartParkRetestretries asignal_account_unreadablepark every 15 minutes viaRetryBlocked(one local probe; server traffic only on success). A lingering false park is now bounded to minutes instead of manual-connect-or-nothing.Guardrails preserved
needs_reauth: a receive-loopnot registered/authorization failedis server-backed evidence — it now takes 2 consecutive confirmations (still parks within seconds for a real unlink), keeps thesignal_account_invalidfingerprint, and is never auto-retried.os.RemoveAlls the signal-cli dir including CDN-expired media./api/signal/connectescape hatch are untouched.Tests
New coverage in
internal/signallive/gate_test.go(StartPoller harness style) andcmd/signal_supervisor_test.go(supervisor-control fake-lifecycle style):signal_account_unreadablepark at streak 3signal_account_invalidsignal_account_unreadablepark automatically and never touches asignal_account_invalidparkGOWORK=off go test ./...: 33 packages pass;-raceclean oninternal/signallive,internal/bridgeadapters/signal,internal/bridge;go vetclean.Runbook: new "Signal
needs_reauth— read the fingerprint before believing the park" section documents the fingerprints and the debug flow.🤖 Generated with Claude Code