feat(feed): publish the Anthropic account uuid on every entry; reap stale leases - #198
feat(feed): publish the Anthropic account uuid on every entry; reap stale leases#198iceteaSA wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
1 issue found across 6 files
Confidence score: 3/5
- In
packages/core/src/quota-header-feed.ts, the reaper’sstat/rmsequence can delete a fresh lease published concurrently, potentially disrupting quota coordination; coordinate reaping with publishing so removal is conditional on the same lease instance.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/core/src/quota-header-feed.ts">
<violation number="1" location="packages/core/src/quota-header-feed.ts:408">
P2: When another process publishes concurrently, this `stat`/`rm` pair can delete its fresh lease. The reaper can stat an old inode, the publisher can rename a fresh file, and `rm(path)` then removes it. Coordinate reaping with publishing or use an atomic compare-and-delete protocol.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| try { | ||
| const file = await stat(path) | ||
| if (file.mtimeMs > now || now - file.mtimeMs < leaseMs) return | ||
| await (this.options.removeFile ?? ((target) => rm(target)))(path) |
There was a problem hiding this comment.
P2: When another process publishes concurrently, this stat/rm pair can delete its fresh lease. The reaper can stat an old inode, the publisher can rename a fresh file, and rm(path) then removes it. Coordinate reaping with publishing or use an atomic compare-and-delete protocol.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/quota-header-feed.ts, line 408:
<comment>When another process publishes concurrently, this `stat`/`rm` pair can delete its fresh lease. The reaper can stat an old inode, the publisher can rename a fresh file, and `rm(path)` then removes it. Coordinate reaping with publishing or use an atomic compare-and-delete protocol.</comment>
<file context>
@@ -350,6 +387,32 @@ export class QuotaHeaderFeedRegistry {
+ try {
+ const file = await stat(path)
+ if (file.mtimeMs > now || now - file.mtimeMs < leaseMs) return
+ await (this.options.removeFile ?? ((target) => rm(target)))(path)
+ } catch {
+ // A missed cleanup must not prevent this process from refreshing its lease.
</file context>
There was a problem hiding this comment.
1 existing issue remains and 1 new issue found across 7 files (changes from recent commits).
Confidence score: 4/5
packages/core/src/accounts.tsmay mishandle a legacy OAuth account when a previously persistedanthropicAccountUuidgains anauthLineageId, causing same-ID re-upserts to be treated as a lineage conflict; add coverage for this legacy-to-lineage transition and verify the intended idempotent behavior.packages/core/src/quota-header-feed.tscan delete a newly replaced lease because thelstatcheck andrmare not atomic, potentially disrupting a sibling process; use an atomic compare-and-delete strategy or revalidate immediately before removal.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/core/src/accounts.ts">
<violation number="1" location="packages/core/src/accounts.ts:3743">
P2: When an existing OAuth account has no `authLineageId` (legacy) but already persisted an `anthropicAccountUuid`, a same-id re-upsert that assigns a lineage for the first time (undefined -> lineage) is treated as `lineageChanged` and deletes the valid UUID even though no credential was replaced. Restrict the fence to genuine replacements by only deleting when the existing account already had a lineage.</violation>
</file>
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Re-trigger cubic
| delete updated.anthropicAccountUuid | ||
| } |
There was a problem hiding this comment.
P2: When an existing OAuth account has no authLineageId (legacy) but already persisted an anthropicAccountUuid, a same-id re-upsert that assigns a lineage for the first time (undefined -> lineage) is treated as lineageChanged and deletes the valid UUID even though no credential was replaced. Restrict the fence to genuine replacements by only deleting when the existing account already had a lineage.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/accounts.ts, line 3743:
<comment>When an existing OAuth account has no `authLineageId` (legacy) but already persisted an `anthropicAccountUuid`, a same-id re-upsert that assigns a lineage for the first time (undefined -> lineage) is treated as `lineageChanged` and deletes the valid UUID even though no credential was replaced. Restrict the fence to genuine replacements by only deleting when the existing account already had a lineage.</comment>
<file context>
@@ -3733,11 +3739,69 @@ export function upsertAccount(
}),
}
+ if (lineageChanged && updated.type === 'oauth') {
+ delete updated.anthropicAccountUuid
+ }
+ storage.accounts[index] = updated
</file context>
| delete updated.anthropicAccountUuid | |
| } | |
| if (lineageChanged && updated.type === 'oauth' && existing.authLineageId !== undefined) { | |
| delete updated.anthropicAccountUuid | |
| } |
…e reaping compare-and-delete
fc80bba to
82105d3
Compare
Resolved packages/core/src/accounts.ts, packages/core/src/quota-header-feed.ts, packages/opencode/src/index.ts, packages/opencode/src/tests/claude-code.test.ts, and packages/opencode/src/tests/index.test.ts. Took PR cortexkit#198's feed schema, projection, validator, and lease semantics; retained custody's branded provider identity and slot quota key separation, with vault > bootstrap > persisted UUID resolution at the canonical feed call sites.
Conflicts resolved: PR cortexkit#198 owns the feed schema, entry projection, validator, lease reaping and the anthropic_account_uuid semantics; this branch keeps the vault-identity plumbing (ProviderAccountUuid brand, quotaKey/providerAccountUuid separation, vault account_id as identity). Includes the post-merge type-contract completion.
Problem
A fallback entry in the quota header feed carries
account_ref = account.id, the store-local id minted at CLI login. Nothing outside this config knows that id, so a consumer joining feed entries to another quota surface (the vault'saccount_id, the usage API) cannot match a fallback account. Main already joins, because #164 keyed it on its bootstrapped Anthropic uuid. Live: the one custodied fallback was unjoinable the moment it started producing traffic.Two smaller things surfaced alongside: the feed directory had 241 lease files, 239 of them older than the 180 s horizon (nothing reaped), and the per-process nature of lease files is undocumented, so a consumer's first reading ("newest file wins") is wrong and flaps.
Change
anthropic_account_uuidon every entry, always present. A uuid string when resolved,nullwhen unresolvable, never absent. Absent means an older producer; null means this producer could not resolve it. Same rule the quota values already follow for no-data vs zero. No schema bump: consumers with version-directed readers keep working.resolveClaudeCodeIdentity); it now rides inservedthrough harvest and persistence to the entry.account_refis unchanged. The uuid is persisted on the account so a cold process publishes it before its first bootstrap. Lineage-fenced like the rest of fallback state: a replaced credential does not inherit the previous login's uuid.lease_horizon_msin the envelope, from the same constantlist()uses for staleness, so consumers stop hardcoding a copy of it.publish()unlinks sibling lease files older than the horizon. Tight name filter (^\d+-[0-9a-f-]+\.json$), fresh and foreign files untouched, subdirectories skipped, future-mtime files kept, ENOENT and sweep failures never fail the publish.lease_horizon_ms, deduplicating by account.Tests
Six mutations proven red first: fallback projection removed; field made optional when unknown; horizon as a literal instead of the constant; persistence dropped (cold process publishes null); sweep skipped; sweep deleting a fresh file. Reviewer (MiniMax M3) added a smuggle probe (
accessToken,tokenHash,claustrumHandle,refreshTokeninjected on the served object: none projected; entry keys are exactly the documented set) and four reaper probes (subdirectory, concurrent publishers, foreign names, future mtime).Gates: core build, root
bun run test1559/0 opencode + 169/0 core, typecheck, format:check, biome. Review: APPROVE, 0 must, 2 should (both about stale-uuid retention across a same-user token rotation; the lineage fence handles the credential-replacement case, and a same-user rotation keeps the uuid correct).Consumer acceptance is pre-registered on the other side: the fallback account goes from unjoinable to joined on
anthropic_account_uuidwith no reader change.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Makes quota feed entries joinable to other quota surfaces by publishing the Anthropic account UUID on every entry, and reaps stale lease files so the feed directory stops accumulating dead leases.
anthropic_account_uuid: a UUID string when resolved,nullwhen not, never absent; absence means an older producer.lease_horizon_msis now included in the envelope so consumers stop hardcoding a copy of it.publish()reclaims sibling lease files older than the horizon, re-checking that a stale file wasn't refreshed concurrently before unlinking; sweep failures never fail the publish.Written for commit 82105d3. Summary will update on new commits.