chadtmiller.com/knotless
git that lives in your Atmosphere account
Spaces renew their credential before it runs out
MergedThis puts a space's credential, or its owner's session, behind a mutex that all clones share, and renews it when it ends within a minute. It looks for a fresh credential on disk first, and otherwise exchanges a new one from the reader's session. The design is sound and fits the root cause in the body: a long-lived Remote kept the credential from open and never renewed it. I found nothing that must change.
- Renewal path:
Held::endingandRenew::renewedmatch whatsession::ensuredoes. The lock is held across the renewal, so concurrent readers make one exchange. A failed renewal leaves the old value in place, so the next read tries again. - Call sites: no
Space::membercall and no use of theauthfield remain.Space::ownerhas the newctxargument at every call site, andspace.auth()is awaited inremote.rsandspace_sync.rs. - Housekeeping: the pull includes the changeset and the
access.mdupdate that AGENTS.md asks for. - Tests: they cover the cases the body names. These are a credential renewed once across clones,
atand another reader via the disk cache, "not logged in" with no session, and an owner session picked up from disk. - Checks: the earlier head (
10c2d7d) failedcheck(clippy),testande2e(build), so I assume the current head fixes the compile and lint errors. At9cd5504,check,test,e2eanddocsare still queued, so I can't confirm that. Wait forcheckandtest, which include the 100% coverage gate, before queueing it. The renewal error paths andSpace::keptare the likeliest places for an uncovered line. - Known limit: as the body says, a space joined from a delegation token alone now fails with "not logged in" once its credential ends. That is accepted.
Since my last review, the only change is f73ccd6. It boxes the Renew::renewed future in Space::held (space.rs) so the large renewal future isn't embedded in every read's future. The behavior is unchanged: the lock is still held across the renewal, and a failed renewal still leaves the old value in place. I found nothing that must change.
check and test are still queued at this head, so I can't confirm they pass. Don't merge before they do, since test includes the coverage gate.
Since my last review there is one new commit, 05a92c5. It makes the test in a_credential_held_past_its_end_is_renewed call listRepos directly with the ended credential and assert the authority answers JwtExpired. I read it as a fix for the test failure at f73ccd6. That run failed the 100% coverage gate, and the only uncovered lines were space.rs:825-827, the stand-in's expired branch, which no test reached. The new call reaches that branch, and it also shows the stand-in refuses an ended credential, so the test proves more than before. I found nothing that must change in the code.
I haven't confirmed the fix. check and test at 05a92c5 were still running when I looked. Before it merges, make sure test passes, in particular that the coverage report no longer lists space.rs lines. The earlier report also listed space.rs:228-229 (the closure in Space::held) among its region-level misses. That is not a failing line, but look at it if the gate still complains.
8 passed, 2 skipped
What changes
A space renews what lets it in. A
Spaceholds its credential, or its owner's session, behind a lock its clones share, and each read takes it throughSpace::authorSpace::for_repo, now async. If it ends within a minute, the read renews it first, the waysession::ensurerenews a session: from the credential kept on disk if another read renewed it already, otherwise with a fresh delegation token and exchange from the reader's session. An owner's session is refreshed throughsession::ensure. All of a space's clones, and everySpace::atof it, share that one renewal.Space::ownertakes theCtxit renews under, andSpace::kepttakes the authority's PDS.Space::memberand the publicauthfield are gone.Why
knotless-run on 178.105.255.120 watches the private
chadtmiller.com/hetzner-infraasinfra.chadtmiller.com, whose space is on a pds.js worker. About ten minutes after it starts, every pass fails withJwtExpired (401): token expiredfromcom.atproto.space.getRecord, until it restarts. The runner opens each watched repository once (Runner::named_watch,Remote::open) and keeps thatRemotein itsWatchfor as long as it runs.Remote::openjoins the space then, so theSpaceit keeps holds the credential from that moment, andRemote::readandget_blobsent it on every read after it expired. The paths that callspace::joinedeach time (the reporter'smine,space_moved,register) kept renewing the credential on disk, so the disk held a fresh one while the passes used the old one. On the box, the credential file was minted at 21:04:54 and valid until 21:14:54, but passes were failing from 21:06. A restart at 21:04 had picked up a credential cached at about 20:56, which still had more than its minute left.runner.knotless.dev does not show this because the repositories it watches are public:
Remote::openfinds their records in the public repo, and noSpaceis involved. A runner that owns the private repository it watches would have hit the same bug once its session's access token expired, becauseSpace::ownerfrozesession.auth(). Both cases are covered.Renewing inside
Spacefixes every long-lived holder at once: the watch, the runs it spawns, and the forks a pass opens. The alternative, re-opening theRemotein the runner at each pass, would leave runs and their readers holding the old credential.How it was checked
Unit tests in
space.rsrun against an axum stand-in for the authority. It hands out ten-minute credentials, counts exchanges, and answerslistReposwithJwtExpiredwhen a credential or session has ended, as the alpha and pds.js do:a_credential_held_past_its_end_is_renewed: a space whose credential ended is renewed with one exchange. Its clones, itsat, and a second space of the same reader pick up that renewal, the second from the disk cache. With no session left, the read fails with "not logged in".an_owner_session_held_past_its_end_is_renewed: an owner's ended session is replaced by the one on disk, with no exchange.signature_basesis rewritten for the new API.The rest is left to CI.
Shortcomings
A space read with a credential that was handed over as a delegation token, with no session of the reader's (the appview's
Remote::delegatedand the no-session branch ofRemote::open), cannot mint another. Once its credential runs out it fails with "not logged in", where before it failed withJwtExpiredfrom the authority. The deployed runner at 178.105.255.120 needs this release before the errors stop.Review
Start at
Space::heldandRenew::renewedincrates/knotless/src/space.rs.🤖 Generated with Claude Code