Description
When the server does not recognize a connector's token it closes the socket with 4001, but the daemon treats that the same as a transient disconnect: it keeps reconnecting on a backoff that tops out around every 30 seconds and never gives up, even though the same token can never be accepted. It also reports the failure as "bad or expired token" although connector tokens have no expiry. The situation is usually triggered by a silent fallback: when a fresh pairing code fails to redeem, the daemon quietly reuses a previously saved token instead of surfacing the failure.
Reproduction
Pair a connector so the saved token file holds a valid token, then re-run the installer against the same server with a pairing code that is no longer redeemable by the time the daemon reaches the redeem step. The daemon connects, is rejected, and keeps reconnecting indefinitely:
[o] online — connected to https://SERVER (root ~/.agent-web/workspaces)
{"connector":"disconnected","code":4001,"reason":"unauthorized","reconnect":true,"retryInMs":1500}
[-] offline — rejected: bad or expired token — create a fresh connector and re-run (code 4001) — reconnecting in 2s
The saved token file's mtime still predates the reinstall by hours, which confirms the fresh redeem never persisted a new token and the daemon connected with the old one. On the server side the reject path in the code closes the socket without emitting a log line, so an operator has little to trace the failure with.
Proposed Plan
Treat a 4001 as terminal rather than transient: stop the reconnect loop and print a single instruction to re-pair, the way an account-suspended close already stops it, since the same token can never be accepted. Stop the daemon from silently reusing a saved token when a pairing was explicitly requested, surfacing the redeem failure and its reason instead. Log the rejection on the server with a redacted token-hash prefix and the remote address, and drop the "expired" wording, because 4001 means the token was not recognized by this server.
Acceptance Criteria
- A 4001 stops the reconnect loop and prints one actionable message to re-pair, instead of reconnecting indefinitely.
- An unrecognized or blank token produces exactly one server log line identifying the attempt by a redacted hash and origin, never the plaintext token.
- Successful pairing and token reconnect flows are unchanged, and no user-facing message calls a connector token expired.
Alternatives
Giving connector tokens a real expiry, a JWT with an exp claim, would make "expired" accurate but is a larger change that still leaves the retry loop and the unlogged reject in place. Extending the pairing-code lifetime reduces how often a code lapses during a slow install, yet the silent fallback and the endless retry remain.
Description
When the server does not recognize a connector's token it closes the socket with 4001, but the daemon treats that the same as a transient disconnect: it keeps reconnecting on a backoff that tops out around every 30 seconds and never gives up, even though the same token can never be accepted. It also reports the failure as "bad or expired token" although connector tokens have no expiry. The situation is usually triggered by a silent fallback: when a fresh pairing code fails to redeem, the daemon quietly reuses a previously saved token instead of surfacing the failure.
Reproduction
Pair a connector so the saved token file holds a valid token, then re-run the installer against the same server with a pairing code that is no longer redeemable by the time the daemon reaches the redeem step. The daemon connects, is rejected, and keeps reconnecting indefinitely:
The saved token file's mtime still predates the reinstall by hours, which confirms the fresh redeem never persisted a new token and the daemon connected with the old one. On the server side the reject path in the code closes the socket without emitting a log line, so an operator has little to trace the failure with.
Proposed Plan
Treat a 4001 as terminal rather than transient: stop the reconnect loop and print a single instruction to re-pair, the way an account-suspended close already stops it, since the same token can never be accepted. Stop the daemon from silently reusing a saved token when a pairing was explicitly requested, surfacing the redeem failure and its reason instead. Log the rejection on the server with a redacted token-hash prefix and the remote address, and drop the "expired" wording, because 4001 means the token was not recognized by this server.
Acceptance Criteria
Alternatives
Giving connector tokens a real expiry, a JWT with an exp claim, would make "expired" accurate but is a larger change that still leaves the retry loop and the unlogged reject in place. Extending the pairing-code lifetime reduces how often a code lapses during a slow install, yet the silent fallback and the endless retry remain.