Skip to content

feat(auth): add provider pool and multi-account management - #1270

Open
yansigit wants to merge 32 commits into
1jehuang:masterfrom
yansigit:feat/provider-pool-account-management-upstream
Open

yansigit wants to merge 32 commits into
1jehuang:masterfrom
yansigit:feat/provider-pool-account-management-upstream

Conversation

@yansigit

@yansigit yansigit commented Sep 15, 2026

Copy link
Copy Markdown

Summary

Adds a provider-scoped account pool for authenticated providers, with durable metadata, safe account selection, quota-aware ranking, cooldowns, leases, failover, and account-management UI/CLI flows.

Closes #1238
Closes #1239
Refs #1130

Categorized changes

  • Storage and security: metadata-only managed account records, native credential separation, migration-safe persistence, no plaintext credential backups, and cross-process locking.
  • Routing and reliability: active-account overrides, quota/history scoring, cooldown persistence, leases, concurrent update safety, request-local failover, and retry-after handling.
  • Provider integrations: OpenAI/Codex, Cursor, and Antigravity account discovery, switching, usage, and quota presentation.
  • User experience: account center, shorthand switching, picker flows, diagnostics, and explicit quota capability semantics.

Validation

  • Rebased on current upstream/master and zero commits behind it.
  • scripts/dev_cargo.sh check -p jcode --bin jcode passed from the final rebased branch.
  • Focused provider-pool, account picker, quota, lease, and credential migration coverage is included in the branch.
  • The published fork branch and PR head now point to the same verified commit.

Scope notes

#1130 describes a broader Anthropic streaming failover report. This PR shares the account-first cooldown, lease, retry-after, and request-local failover foundation, but references rather than closes that issue because not every Anthropic-specific streaming behavior is claimed here.

@greptile-apps

greptile-apps Bot commented Sep 15, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 1/5

Not safe to merge: existing blocking account-routing, persistence, and streamed-failover issues remain unresolved.

Findings

  1. P1 Restore rotated credentials
  2. P1 Lock account files on Windows
  3. P1 Make credential writes atomic
  4. P1 Handle streamed failures
  5. P2 Support removing pooled accounts
Fix with agent prompt
### Issue 1
crates/jcode-provider-openai-runtime/src/openai_provider_impl.rs:undefined-58
When a stream rotates after an authentication or quota failure, this replaces the shared runtime credentials but leaves the selected account label unchanged. Nothing restores the original credentials when the stream ends, so later requests and prewarms can use the alternate account while the UI and usage reporting still identify the original account.

### Issue 2
crates/jcode-base/src/auth/provider_pool.rs:452-458
On Windows, these account and health locks open their lock files but never acquire an operating-system lock. Two jcode processes can therefore read the same pool state and publish conflicting updates, causing imported accounts, active selections, cooldowns, or quota history to be lost. Add Windows inter-process locking around the full read-modify-write cycle.

### Issue 3
crates/jcode-base/src/auth/provider_pool.rs:undefined-521
Credential values are written to native storage before the metadata file is published, with no rollback if metadata publication fails. For example, a disk-full or permission error while updating an existing account can leave the old metadata pointing at newly replaced credentials; a new account can instead leave orphaned secrets. Stage or reconcile both stores so a failed save cannot partially update an account.

### Issue 4
crates/jcode-base/src/provider/dispatch.rs:44-48
The wrapper forwards `Some(Err(_))` without invoking account or provider failover, while Cursor starts its network operation only after returning an event stream. Cursor transport, authentication, and quota failures therefore bypass the new same-provider failover path, and the user receives the original error without trying another pooled account.

### Issue 5
src/cli/args.rs:1067-1079
This is non-blocking, but managed Cursor and Antigravity accounts can be listed and switched without any removal action in the CLI, TUI, picker, or `provider_pool`. Users cannot remove revoked or unwanted imported credentials and metadata through the account-management flow, creating unnecessary manual cleanup work.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This update adds provider-scoped multi-account management and account-aware request routing for OpenAI, Cursor, and Antigravity. It separates managed-account metadata from native credential storage, adds quota-aware selection, cooldowns, leases, and failover behavior, and expands CLI/TUI account-management surfaces.

Blocking account-routing, persistence, and streamed-failure issues from the existing review remain unresolved. Managed Cursor and Antigravity accounts also still cannot be removed through the account-management flow.

Reviews (2) · Last reviewed commit: "fix: secure standalone provider credenti..."

Comment thread crates/jcode-base/src/provider/antigravity.rs
return None;
}
};
*credentials.write().await = next;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Restore rotated credentials

When a stream rotates after an authentication or quota failure, this replaces the shared runtime credentials but leaves the selected account label unchanged. Nothing restores the original credentials when the stream ends, so later requests and prewarms can use the alternate account while the UI and usage reporting still identify the original account.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-openai-runtime/src/openai_provider_impl.rs
Line: 58

Comment:
**Restore rotated credentials**

When a stream rotates after an authentication or quota failure, this replaces the shared runtime credentials but leaves the selected account label unchanged. Nothing restores the original credentials when the stream ends, so later requests and prewarms can use the alternate account while the UI and usage reporting still identify the original account.

**Knowledge Base Used:**
- [Authentication and account management](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/authentication-and-account-management.md)
- [Provider selection and runtime adapters](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/provider-selection-and-runtime-adapters.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +452 to +458
#[cfg(unix)]
{
let operation = if shared { libc::LOCK_SH } else { libc::LOCK_EX };
if unsafe { libc::flock(file.as_raw_fd(), operation) } != 0 {
return None;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Lock account files on Windows

On Windows, these account and health locks open their lock files but never acquire an operating-system lock. Two jcode processes can therefore read the same pool state and publish conflicting updates, causing imported accounts, active selections, cooldowns, or quota history to be lost. Add Windows inter-process locking around the full read-modify-write cycle.

Knowledge Base Used: Authentication and account management

Artifacts

Unix account lease test

  • Ran the existing account-lease cross-process test on Linux; it passed by blocking the first child and allowing the second after release, showing only the Unix flock path works.

Windows lock platform proof

  • Executed the focused source-level platform proof; it shows both account and health lock acquire paths return an unlocked handle when Unix-only code is excluded, confirming the Windows defect.

Windows lock proof script

  • Authored and executed a Python proof that extracts both acquire implementations and validates their Windows-effective code paths contain no lock operation, confirming the missing Windows synchronization.

Installed Rust targets

  • Checked installed Rust targets; only the Linux target is present, so a native Windows runtime reproduction was unavailable.

Product code unchanged

  • Checked the reviewed product file for a diff after validation; no product-code edit was made.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/auth/provider_pool.rs
Line: 452-458

Comment:
**Lock account files on Windows**

On Windows, these account and health locks open their lock files but never acquire an operating-system lock. Two jcode processes can therefore read the same pool state and publish conflicting updates, causing imported accounts, active selections, cooldowns, or quota history to be lost. Add Windows inter-process locking around the full read-modify-write cycle.

**Knowledge Base Used:** [Authentication and account management](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/authentication-and-account-management.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

}

fn write_unlocked(provider: &str, file: &AccountFile) -> Result<()> {
let persisted = PersistedAccountFile::from_managed(provider, file)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Make credential writes atomic

Credential values are written to native storage before the metadata file is published, with no rollback if metadata publication fails. For example, a disk-full or permission error while updating an existing account can leave the old metadata pointing at newly replaced credentials; a new account can instead leave orphaned secrets. Stage or reconcile both stores so a failed save cannot partially update an account.

Knowledge Base Used: Authentication and account management

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/auth/provider_pool.rs
Line: 521

Comment:
**Make credential writes atomic**

Credential values are written to native storage before the metadata file is published, with no rollback if metadata publication fails. For example, a disk-full or permission error while updating an existing account can leave the old metadata pointing at newly replaced credentials; a new account can instead leave orphaned secrets. Stage or reconcile both stores so a failed save cannot partially update an account.

**Knowledge Base Used:** [Authentication and account management](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/authentication-and-account-management.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +44 to +48
let item = self.inner.as_mut().poll_next(context);
if matches!(item, Poll::Ready(None)) {
self.account_lease = None;
}
item

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Handle streamed failures

The wrapper forwards Some(Err(_)) without invoking account or provider failover, while Cursor starts its network operation only after returning an event stream. Cursor transport, authentication, and quota failures therefore bypass the new same-provider failover path, and the user receives the original error without trying another pooled account.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/dispatch.rs
Line: 44-48

Comment:
**Handle streamed failures**

The wrapper forwards `Some(Err(_))` without invoking account or provider failover, while Cursor starts its network operation only after returning an event stream. Cursor transport, authentication, and quota failures therefore bypass the new same-provider failover path, and the user receives the original error without trying another pooled account.

**Knowledge Base Used:**
- [Model provider integration](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/provider-integration.md)
- [Provider selection and runtime adapters](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/provider-selection-and-runtime-adapters.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread src/cli/args.rs
Comment on lines +1067 to +1079
/// List pooled provider accounts or persistently select one account
Accounts {
/// Provider to inspect: openai, cursor, antigravity, or all
#[arg(id = "pool_provider", default_value = "all", value_name = "PROVIDER")]
pool_provider: String,

/// Persistently select an account by its displayed label
#[arg(long, value_name = "LABEL")]
switch: Option<String>,

/// Emit JSON instead of plain text
#[arg(long)]
json: bool,

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 Support removing pooled accounts

This is non-blocking, but managed Cursor and Antigravity accounts can be listed and switched without any removal action in the CLI, TUI, picker, or provider_pool. Users cannot remove revoked or unwanted imported credentials and metadata through the account-management flow, creating unnecessary manual cleanup work.

Knowledge Base Used: Authentication and account management

Artifacts

Managed account removal audit

  • A non-mutating authored script inspected the CLI, TUI, and provider-pool removal paths; it provides the repeatable evidence command.

Managed account removal audit output

  • The executed audit prints the relevant CLI fields and dispatch plus TUI dispatch and picker code, showing no managed removal implementation; the takeaway is that the removal path is absent.

Managed account removal check

  • A non-mutating authored CLI script requests account help and then attempts the unsupported `--remove` option; it is ready to reproduce the parser behavior once the workspace build is available.

CLI removal invocation attempt

  • The real CLI invocation with `auth accounts cursor --remove candidate-account --json` was attempted under a 15-second timeout; concurrent Cargo builds prevented completion, so it provides no contrary runtime result.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/args.rs
Line: 1067-1079

Comment:
**Support removing pooled accounts**

This is non-blocking, but managed Cursor and Antigravity accounts can be listed and switched without any removal action in the CLI, TUI, picker, or `provider_pool`. Users cannot remove revoked or unwanted imported credentials and metadata through the account-management flow, creating unnecessary manual cleanup work.

**Knowledge Base Used:** [Authentication and account management](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/authentication-and-account-management.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@yansigit
yansigit force-pushed the feat/provider-pool-account-management-upstream branch from 0656752 to c84657c Compare September 16, 2026 01:53
@greptile-apps

greptile-apps Bot commented Sep 16, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

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.

feat: expose Cursor account quota and usage semantics feat: secure provider credential storage and legacy migration

1 participant