Skip to content

Stop transient boot-time probe failures from permanently parking Signal needs_reauth - #170

Merged
MaxGhenis merged 1 commit into
mainfrom
claude/zen-carson-1baf29
Aug 22, 2026
Merged

Stop transient boot-time probe failures from permanently parking Signal needs_reauth#170
MaxGhenis merged 1 commit into
mainfrom
claude/zen-carson-1baf29

Conversation

@MaxGhenis

Copy link
Copy Markdown
Owner

Problem

The Signal bridge recurrently latched a permanent needs_reauth park from a transient boot-time failure on a link that was actually valid. Three live episodes on the production install:

Root cause: runReceiveLoop's startup probe ran one listAccounts and treated an empty success (err == nil && account == "") as terminal proof of an unlinked account (last_error: "Signal account is not paired", needs_reauth: truePollerFailureReauth → supervisor StateBlocked, forever). But listAccounts is local-only evidence: signal-cli racing its own account bootstrap at backend boot logs Ignoring <number>: User is not registered. and exits 0 with zero accounts — the exact signature the existing TestParseSignalAccountsIgnoresSignalCLIErrorOutput fixture 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.json also lists no account (locally gone — unchanged semantics, same signal_account_invalid fingerprint). While accounts.json still 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 fingerprint signal_account_unreadable.

(c) The local-evidence park self-retestssignalSupervisorControl.StartParkRetest retries a signal_account_unreadable park every 15 minutes via RetryBlocked (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

  • Genuine server-side unlink still surfaces needs_reauth: a receive-loop not registered/authorization failed is server-backed evidence — it now takes 2 consecutive confirmations (still parks within seconds for a real unlink), keeps the signal_account_invalid fingerprint, and is never auto-retried.
  • Nothing auto-unpairs, ever — unpair os.RemoveAlls the signal-cli dir including CDN-expired media.
  • Pairing/upgrade parks and the manual /api/signal/connect escape hatch are untouched.

Tests

New coverage in internal/signallive/gate_test.go (StartPoller harness style) and cmd/signal_supervisor_test.go (supervisor-control fake-lifecycle style):

  • boot-race empty probe retries and connects (no park, 2 probe calls)
  • empty probe with stored account: transient × 2 → signal_account_unreadable park at streak 3
  • a successful probe resets the streak
  • single receive account-invalid glitch keeps the loop connected; consecutive ones park under signal_account_invalid
  • the park retest exits an signal_account_unreadable park automatically and never touches a signal_account_invalid park
  • existing probe-park test updated for the retry attempts (final classification unchanged)

GOWORK=off go test ./...: 33 packages pass; -race clean on internal/signallive, internal/bridgeadapters/signal, internal/bridge; go vet clean.

Runbook: new "Signal needs_reauth — read the fingerprint before believing the park" section documents the fingerprints and the debug flow.

🤖 Generated with Claude Code

…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>
@MaxGhenis
MaxGhenis merged commit 39576f3 into main Aug 22, 2026
5 checks passed
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)
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