feat(mcp): sweep expired codes and tokens on a timer #112

Merged
Claude-Run merged 1 commit from feat/YT-54-sweep-expired-timer into main 2026-07-17 01:53:36 +02:00
Member

db::sweep_expired deletes spent authorization codes and expired access and refresh tokens, and its doc comment said it was "intended to run on a timer", but that timer was never wired up: the only callers were a unit test and the module doc. Spent codes and expired tokens therefore accumulated for the lifetime of a deployment. Expiry is enforced at lookup time so the stale rows were never honored, but they meant unbounded database growth, retention of token hashes long past their usefulness, and index bloat on the hot lookup paths.

yt mcp serve now spawns a background task that sweeps at startup and every hour thereafter. An hour sits well under the 3600s access-token TTL, so the backlog between ticks stays small, and the sweep is three indexed DELETEs against a local SQLite file. The startup tick also prunes whatever expired while the service was down, and the task is aborted when the server stops. A failing sweep is transient, so it is logged at warn and the loop keeps its schedule rather than propagating and taking the service down.

The sweep covers only the tables sweep_expired already covered. hub_tokens stays excluded: a row past expires_at is still needed because its refresh token mints the replacement, so sweeping those would log every user out every hour. Clearing them is deliberate recovery, not hygiene. A test asserts an expired Hub token survives the sweep, so the exclusion fails loudly if anyone adds it to sweep_expired.

yt mcp stdio holds no database and spawns no sweep.

#YT-54

`db::sweep_expired` deletes spent authorization codes and expired access and refresh tokens, and its doc comment said it was "intended to run on a timer", but that timer was never wired up: the only callers were a unit test and the module doc. Spent codes and expired tokens therefore accumulated for the lifetime of a deployment. Expiry is enforced at lookup time so the stale rows were never honored, but they meant unbounded database growth, retention of token hashes long past their usefulness, and index bloat on the hot lookup paths. `yt mcp serve` now spawns a background task that sweeps at startup and every hour thereafter. An hour sits well under the 3600s access-token TTL, so the backlog between ticks stays small, and the sweep is three indexed DELETEs against a local SQLite file. The startup tick also prunes whatever expired while the service was down, and the task is aborted when the server stops. A failing sweep is transient, so it is logged at warn and the loop keeps its schedule rather than propagating and taking the service down. The sweep covers only the tables `sweep_expired` already covered. `hub_tokens` stays excluded: a row past `expires_at` is still needed because its refresh token mints the replacement, so sweeping those would log every user out every hour. Clearing them is deliberate recovery, not hygiene. A test asserts an expired Hub token survives the sweep, so the exclusion fails loudly if anyone adds it to `sweep_expired`. `yt mcp stdio` holds no database and spawns no sweep. #YT-54
feat(mcp): sweep expired codes and tokens on a timer
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 41s
Create release / Create release from merged PR (pull_request) Has been skipped
062fc2fd58
`db::sweep_expired` deletes spent authorization codes and expired access and refresh tokens, and its doc comment said it was "intended to run on a timer", but that timer was never wired up: the only callers were a unit test and the module doc. Spent codes and expired tokens therefore accumulated for the lifetime of a deployment. Expiry is enforced at lookup time so the stale rows were never honored, but they meant unbounded database growth, retention of token hashes long past their usefulness, and index bloat on the hot lookup paths.

`yt mcp serve` now spawns a background task that sweeps at startup and every hour thereafter. An hour sits well under the 3600s access-token TTL, so the backlog between ticks stays small, and the sweep is three indexed DELETEs against a local SQLite file. The startup tick also prunes whatever expired while the service was down, and the task is aborted when the server stops. A failing sweep is transient, so it is logged at warn and the loop keeps its schedule rather than propagating and taking the service down.

The sweep covers only the tables `sweep_expired` already covered. `hub_tokens` stays excluded: a row past `expires_at` is still needed because its refresh token mints the replacement, so sweeping those would log every user out every hour. Clearing them is deliberate recovery, not hygiene. A test asserts an expired Hub token survives the sweep, so the exclusion fails loudly if anyone adds it to `sweep_expired`.

`yt mcp stdio` holds no database and spawns no sweep.

#YT-54
Claude-Run deleted branch feat/YT-54-sweep-expired-timer 2026-07-17 01:53:36 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pandoras-box/youtrack-cli!112
No description provided.