fix(usage): org-qualified session marker for same-email accounts

The /usage show "in use by this session:" marker took only the bare
email from OAuthAccountIdentity, so two same-email Anthropic credentials
in different orgs were indistinguishable. Route the label through a
shared formatActiveAccountLabel that suffixes the active org, matching
the account list and login-success surfaces.

Fixes #5691
This commit is contained in:
roboomp
2026-07-16 11:09:05 +00:00
parent 6ae7cdbf97
commit dc372c67d6
4 changed files with 53 additions and 2 deletions
+4
View File
@@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed the `/usage show` `in use by this session:` marker showing only the login email, so two same-email Anthropic credentials in different orgs (a Team seat and a personal Max plan) were indistinguishable. The marker now suffixes the active organization (`email (OrgName)`) via a shared `formatActiveAccountLabel`, matching the account list and login-success surfaces ([#5691](https://github.com/can1357/oh-my-pi/issues/5691)).
## [17.0.1] - 2026-07-16
### Changed
@@ -43,7 +43,7 @@ import type { AuthStorage, OAuthAccountIdentity } from "../../session/auth-stora
import type { CompactMode } from "../../session/compact-modes";
import type { NewSessionOptions } from "../../session/session-entries";
import { formatShakeSummary, type ShakeMode, type ShakeResult } from "../../session/shake-types";
import { limitMatchesActiveAccount } from "../../slash-commands/helpers/active-oauth-account";
import { formatActiveAccountLabel, limitMatchesActiveAccount } from "../../slash-commands/helpers/active-oauth-account";
import { outputMeta } from "../../tools/output-meta";
import { resolveToCwd, stripOuterDoubleQuotes } from "../../tools/path-utils";
import { replaceTabs, truncateToWidth } from "../../tools/render-utils";
@@ -1642,7 +1642,7 @@ export function renderUsageReports(
}
lines.push(uiTheme.bold(uiTheme.fg("accent", providerName)));
const activeAccountLabel = activeAccount?.email ?? activeAccount?.accountId ?? activeAccount?.projectId;
const activeAccountLabel = formatActiveAccountLabel(activeAccount);
if (activeAccountLabel) {
lines.push(` ${uiTheme.fg("accent", "in use by this session:")} ${activeAccountLabel}`);
}
@@ -5,6 +5,22 @@ function normalizeIdentityValue(value: unknown): string | undefined {
return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : undefined;
}
/**
* Session marker label for an active OAuth identity: the base identifier
* (email → accountId → projectId) suffixed with the organization when present
* and distinct. Same-email Anthropic multi-org accounts share the base, so the
* org suffix is the only field that tells the session's quota pool apart —
* mirrors the account-list rows (`formatUsageReportAccount`) and login success.
* Returns `undefined` when no identifier is recoverable.
*/
export function formatActiveAccountLabel(identity: OAuthAccountIdentity | undefined): string | undefined {
if (!identity) return undefined;
const base = identity.email || identity.accountId || identity.projectId;
if (!base) return undefined;
const org = identity.orgName || identity.orgId;
return org && org !== base ? `${base} (${org})` : base;
}
/**
* True when a single usage-limit column belongs to the given OAuth identity.
*
@@ -82,3 +82,34 @@ describe("renderUsageReports (#3268 TUI aggregate)", () => {
expect(occurrences).toBe(1);
});
});
describe("renderUsageReports session marker (#5691 org-qualified identity)", () => {
it("suffixes the active org so same-email multi-org accounts are tellable apart", () => {
const email = "dev@example.test";
const reports: UsageReport[] = [
report("anthropic", email, [limit("Claude 7 Day", "weekly", 7 * 24 * HOUR, 0.4)]),
];
const text = stripVTControlCharacters(
renderUsageReports(reports, theme, Date.now(), 120, provider =>
provider === "anthropic" ? { email, orgId: "uuid-A", orgName: "Team Org" } : undefined,
),
);
const marker = text.split("\n").find(line => line.includes("in use by this session"));
expect(marker).toContain(`${email} (Team Org)`);
});
it("falls back to the bare base when the active identity carries no org", () => {
const email = "solo@example.test";
const reports: UsageReport[] = [
report("anthropic", email, [limit("Claude 7 Day", "weekly", 7 * 24 * HOUR, 0.4)]),
];
const text = stripVTControlCharacters(
renderUsageReports(reports, theme, Date.now(), 120, provider =>
provider === "anthropic" ? { email } : undefined,
),
);
const marker = text.split("\n").find(line => line.includes("in use by this session"));
expect(marker).toContain(email);
expect(marker).not.toContain("(");
});
});