chadtmiller.com/knotless
git that lives in your Atmosphere account
A connector whose login has ended has its user sign in again
MergedThis pull makes the server treat a connector as signed out once its PDS refuses to refresh the stored session. bearer answers 401 and the token endpoint answers invalid_grant, and the dead session is removed. Any other refresh failure is logged at warn and the call goes on.
I read the diff and the surrounding session.rs and oauth.rs. The invalid_grant match works. oauth::post formats a refused refresh as "<error>: <description>", so said.starts_with(INVALID_GRANT) holds for a real PDS refusal. A PDS that can't be reached fails earlier with a transport error, which doesn't match, so the session stays. oauth::refresh serialises on the session lock and reuses a session another caller already refreshed, so concurrent /mcp calls shouldn't race each other into removing a good session. The change file and both docs pages are updated, as AGENTS.md asks. The test covers the refresh, the unreachable PDS, and the 401 plus invalid_grant outcome, though I didn't run it and haven't seen CI.
Two small notes, neither blocking:
endedtreats "no stored session" as over, so any account with a valid connector token but no session file now gets401. That seems intended, since the tools would otherwise read as no one.bearernow does a file read, and a refresh when the token is expiring, on every request. Cheap in the common case.
The only change since my approval is the last commit, which makes the dead-login test edit the stored session by its real field names (oauth.expiresAt, oauth.tokenEndpoint, refreshJwt). That fits the flow the test checks: a refresh when the login is expiring, a PDS that can't be reached leaving the login in place, then a refused refresh giving 401, removing the login, and giving invalid_grant on the token refresh. The implementation, change file and docs are unchanged from what I reviewed. I didn't run the test. The queue is running the checks, and they will show whether it passes.
8 passed, 2 skipped
What changes
A connector whose login has ended asks to sign in again. On a server, once a PDS turns down refreshing the login kept for an account, the server forgets that login. It answers the next
/mcpcall401and the next token refreshinvalid_grant, which are how an MCP client learns to sign in again.Why a refresh failed is now logged, at warn. A refresh that fails any other way, such as a PDS that cannot be reached, leaves the login in place and the call goes on.
Why
A server holds two logins: the connector's token from the server, which renews for 90 days from each refresh, and the server's session with the PDS, which the PDS ends when it chooses. The second could end while the first kept working. Tools then fell back to reading as no one, so public repositories worked, private ones answered
no such repository, and writes said to runknotless login, which does nothing for a hosted connector. This was found on the owner's own connector, which had been failing like this since its session reached a PDS's 14-day limit.The check is in
bearerand the token endpoint, not in each tool, so every tool is covered and the anonymous fallback is never reached on a dead login. Lengthening sessions at the PDS or matching the token lifetimes would not do: each PDS sets its own limit and does not say what it is.How it was checked
The main HTTP test in
crates/knotless-mcp/tests/http.rsgoes on against the reference PDS: a login running out is refreshed, a PDS that cannot be reached leaves it in place, and one turned down answers401, removes it and refuses the token refresh. Left to CI, nothing built here.Shortcomings
How Claude's connector shows a
401mid-session has not been seen, and is to be tried on the owner's account after deploy. A refresh that fails for a passing reason still reads as no one for that call, as it did. A session revoked at the PDS while its access token is still good is not noticed until that token runs out, up to an hour on some servers.🤖 Generated with Claude Code