Skip to content

feat(feed): publish the Anthropic account uuid on every entry; reap stale leases - #198

Open
iceteaSA wants to merge 3 commits into
cortexkit:mainfrom
iceteaSA:feat/feed-account-uuid
Open

feat(feed): publish the Anthropic account uuid on every entry; reap stale leases#198
iceteaSA wants to merge 3 commits into
cortexkit:mainfrom
iceteaSA:feat/feed-account-uuid

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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's account_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_uuid on every entry, always present. A uuid string when resolved, null when 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.
  • Fallback projection, not a fetch. The served token's uuid is already resolved per request (resolveClaudeCodeIdentity); it now rides in served through harvest and persistence to the entry. account_ref is 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_ms in the envelope, from the same constant list() uses for staleness, so consumers stop hardcoding a copy of it.
  • Reaper. 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.
  • Contract doc (README + schema comment): lease files are per process and each carries only the accounts whose response headers that process harvested. Consumers must union across all files inside 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, refreshToken injected 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 test 1559/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_uuid with no reader change.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with 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.

  • Every entry now carries anthropic_account_uuid: a UUID string when resolved, null when not, never absent; absence means an older producer.
  • Fallback entries persist the served token's UUID on the account so a cold process publishes it before bootstrap; a replaced credential does not inherit the previous login's UUID, while a same-lineage rotation keeps it.
  • lease_horizon_ms is 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.
  • README documents that lease files are per process and consumers must union across all files within the horizon, deduplicating by account.

Written for commit 82105d3. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files

Confidence score: 3/5

  • In packages/core/src/quota-header-feed.ts, the reaper’s stat/rm sequence 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

Comment thread packages/opencode/src/index.ts Outdated
Comment thread packages/core/src/accounts.ts
try {
const file = await stat(path)
if (file.mtimeMs > now || now - file.mtimeMs < leaseMs) return
await (this.options.removeFile ?? ((target) => rm(target)))(path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Comment thread packages/opencode/src/index.ts Outdated
Comment thread packages/opencode/src/tests/index.test.ts Outdated
Comment thread README.md Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 existing issue remains and 1 new issue found across 7 files (changes from recent commits).

Confidence score: 4/5

  • packages/core/src/accounts.ts may mishandle a legacy OAuth account when a previously persisted anthropicAccountUuid gains an authLineageId, 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.ts can delete a newly replaced lease because the lstat check and rm are 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

Comment on lines +3743 to +3744
delete updated.anthropicAccountUuid
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
delete updated.anthropicAccountUuid
}
if (lineageChanged && updated.type === 'oauth' && existing.authLineageId !== undefined) {
delete updated.anthropicAccountUuid
}

@iceteaSA
iceteaSA force-pushed the feat/feed-account-uuid branch from fc80bba to 82105d3 Compare September 4, 2026 17:09
iceteaSA added a commit to iceteaSA/anthropic-auth that referenced this pull request Sep 7, 2026
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.
iceteaSA added a commit to iceteaSA/anthropic-auth that referenced this pull request Sep 7, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant