Clear old localhost cookies when issuing new ones#36
Conversation
|
Claude finished @calvinstudebaker's task in 1m 58s —— View job Code review: Clear old localhost cookies when issuing new onesRead the full diff and 🟠 Concurrent dev servers get swept (see inline comment)
This directly contradicts the 🟡 Session cookies become persistent (1 day)Adding 🔵 Minor
✅ Verified fine
Verdict: Solid, well-documented change that fixes the jar-overflow problem. The only thing I'd resolve before merge is the concurrent-server interaction — confirm it's acceptable or guard against it.
|
| // Each dev run mints a fresh build uuid, so its cookies carry new names; | ||
| // host-scoped, they replay to every localhost server (any port) and never | ||
| // self-clear. Expire prior builds' leftovers here — the one request that | ||
| // sees them all — so the jar stays at this build's three cookies instead | ||
| // of growing until it overflows some localhost app's request headers. | ||
| for stale in stale_wavedash_cookies(&headers, &cfg) { | ||
| builder = builder.header(header::SET_COOKIE, expire_cookie(&stale)); | ||
| } |
There was a problem hiding this comment.
This sweep breaks concurrently running dev servers, which the build-uuid scheme was explicitly designed to support.
The ServeConfig.build_uuid doc comment (lines 43–44) states the per-build cookie names exist "to keep concurrent and successive dev servers from clobbering each other." Cookies are host-scoped and ignore ports, so the browser replays every localhost server's wd_* cookies to this one.
stale_wavedash_cookies treats any wd_* cookie that isn't this build's three as stale — it can't distinguish "orphaned by a dead server" from "live, belonging to another dev server running right now on a different port." So if build A and build B run at the same time and you complete a handoff on B, B's callback expires A's wd_session_/wd_jwt_/wd_sdkconfig_ cookies, killing A's authenticated session.
Before this PR, concurrent servers coexisted (each only ever wrote its own uuid-named cookies); this introduces cross-build deletion. Build A recovers on its next reload (handoff re-runs), so it's a degradation rather than a hard break — but it does undercut the stated concurrency guarantee.
If concurrent dev servers are no longer a supported scenario, consider updating the lines 43–44 comment to say so. If they are still supported, the sweep needs a way to tell live builds apart from orphans — e.g. a small shared registry/lockfile of active build uuids that each server adds itself to on startup and removes on shutdown, so the sweep only expires uuids not in that set.
|
Claude finished @calvinstudebaker's task in 3m 49s —— View job Code review: Clear old localhost cookies when issuing new onesRe-reviewed after the latest push (which removes 🟠 Concurrent dev servers still get swept (open thread on the sweep)
This directly contradicts the Two clean ways forward:
🟡 Session cookies are now persistent (1 day)Adding 🔵 Minor
✅ Verified fine
Verdict: Solid, well-documented change that fixes the jar-overflow problem. The only thing I'd resolve before merge is the concurrent-server interaction — confirm it's acceptable (and relax the
|
| /// The deletion form (empty value, immediate expiry) of `set_cookie`; Path and | ||
| /// attributes must match for the browser to drop it. |
There was a problem hiding this comment.
🔵 Minor (comment accuracy): per RFC 6265 a cookie is identified for replacement by name + Path + Domain only — HttpOnly, SameSite, value and Max-Age play no part in the match. So "Path and attributes must match" slightly overstates it: keeping the attributes here is harmless, but it's the matching Path=/ (and absent Domain, same as set_cookie) that actually makes the browser drop the cookie.
| /// The deletion form (empty value, immediate expiry) of `set_cookie`; Path and | |
| /// attributes must match for the browser to drop it. | |
| /// The deletion form (empty value, immediate expiry) of `set_cookie`. A cookie | |
| /// is matched for replacement by name + Path + Domain only, so the shared | |
| /// `Path=/` (and no Domain) is what makes the browser drop it. |
…server has a lock
|
Claude finished @calvinstudebaker's task in 2m 16s —— View job Code review: Clear old localhost cookies when issuing new onesRe-reviewed after the latest push ( ✅ The concurrent-server concern is resolvedThe earlier worry — that
This is a clean, correct fix. 🔵 Minor — open-failure path is more permissive than the lock-failure path
🔵 Minor — own lockfile isn't removed on clean exitA server never deletes its own 🔵 Pre-existing nits (carried over, optional)
✅ Also verified
Verdict: The blocking concern from the previous reviews is resolved — the lockfile registry correctly distinguishes live concurrent servers from orphans, and the rest of the change is solid and well-documented. Remaining items are all minor/optional. LGTM. 👍
|
| let Ok(file) = std::fs::OpenOptions::new().read(true).open(&path) else { | ||
| return false; | ||
| }; |
There was a problem hiding this comment.
🔵 Minor (defensive consistency): the open path here treats any error as "exited" → false, which sweeps that build's cookies. But the try_lock arms below are deliberately conservative (TryLockError::Error(_) => true, "never sweep a build we can't prove is gone"). So a non-NotFound open failure (e.g. a transient EACCES/FS error) would sweep a build that might still be live — the opposite of the stated principle.
In practice these are files the same user just created on local disk, so a non-NotFound error is near-impossible and this is very low severity. If you want the two paths to agree, distinguish the not-found case:
let file = match std::fs::OpenOptions::new().read(true).open(&path) {
Ok(f) => f,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return false, // owner exited, lockfile gone
Err(_) => return true, // can't prove it's gone → keep, same as the lock-error arm
};There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 24856ff. Configure here.
| fn build_is_live(sessions_dir: &Path, uuid: &str) -> bool { | ||
| let path = sessions_dir.join(format!("{uuid}.lock")); | ||
| let Ok(file) = std::fs::OpenOptions::new().read(true).open(&path) else { | ||
| return false; |
There was a problem hiding this comment.
Missing lock treats live server dead
Medium Severity
build_is_live returns dead when the session lock file cannot be opened, but a dev server can still be running without that file (older CLI without locks, or the lock file removed while the process keeps its handle). Another localhost dev auth callback may then expire that build’s wd_* cookies and break its session.
Reviewed by Cursor Bugbot for commit 24856ff. Configure here.


localhost cookie jar was getting too large
Note
Medium Risk
Changes local dev auth cookie lifecycle and lock-based sweeps; mistakes could clear active sessions or leave stale cookies, but scope is localhost dev only—not production auth.
Overview
Repeated
wavedash devruns mint new build UUIDs and newwd_*_<uuid>cookies that all share the localhost jar, so old cookies piled up and could bloat request headers.Each dev server now holds a
<uuid>.lockunderdev-sessionsfor its lifetime (released on exit/crash). On the auth callback, the server scans incoming cookies, expireswd_session_/wd_jwt_/wd_sdkconfig_names for builds that are not the current run and whose lock is absent or acquirable (dead server), and skips cookies for builds that still hold the lock (concurrent live servers).New session cookies use shared
set_cookie/expire_cookiehelpers with 1-dayMax-Ageso orphaned sessions eventually age out even without another handoff.get_temp_credentialsis refactored to take aBuildUploadInfostruct; local Claude settings are removed; a redundanttotal_bytesbinding in the uploader is dropped.Reviewed by Cursor Bugbot for commit 24856ff. Bugbot is set up for automated code reviews on this repo. Configure here.