instar 1.3.643 → 1.3.645
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/CredentialIdentityOracle.d.ts +3 -2
- package/dist/core/CredentialIdentityOracle.d.ts.map +1 -1
- package/dist/core/CredentialIdentityOracle.js +10 -4
- package/dist/core/CredentialIdentityOracle.js.map +1 -1
- package/dist/core/OAuthRefresher.d.ts +25 -0
- package/dist/core/OAuthRefresher.d.ts.map +1 -1
- package/dist/core/OAuthRefresher.js +110 -7
- package/dist/core/OAuthRefresher.js.map +1 -1
- package/dist/core/QuotaPoller.d.ts +9 -3
- package/dist/core/QuotaPoller.d.ts.map +1 -1
- package/dist/core/QuotaPoller.js +8 -4
- package/dist/core/QuotaPoller.js.map +1 -1
- package/dist/monitoring/CredentialProvider.d.ts.map +1 -1
- package/dist/monitoring/CredentialProvider.js +23 -3
- package/dist/monitoring/CredentialProvider.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.644.md +45 -0
- package/upgrades/1.3.645.md +50 -0
- package/upgrades/eli16/credential-keychain-async-read.md +44 -0
- package/upgrades/eli16/keychain-residual-async-read.md +29 -0
- package/upgrades/side-effects/credential-keychain-async-read.md +60 -0
- package/upgrades/side-effects/keychain-residual-async-read.md +70 -0
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* missing-or-empty-or-nonstring email) → `unavailable` with a reason. NEVER a "mismatch": an
|
|
19
19
|
* unverifiable slot is quarantine-never-repair upstream, never a guess.
|
|
20
20
|
*
|
|
21
|
-
* Reuses `
|
|
22
|
-
* access
|
|
21
|
+
* Reuses `readClaudeOauthAsync` (OAuthRefresher) for the per-slot blob read — no hand-rolled
|
|
22
|
+
* keychain access, and NON-BLOCKING so the sequential audit loop never freezes the event loop on a
|
|
23
|
+
* slow `securityd`. The profile fetch lives here behind a bounded timeout; this file is allowlisted in
|
|
23
24
|
* `scripts/lint-no-direct-llm-http.js` for the SAME reason QuotaCollector is — an OAuth profile
|
|
24
25
|
* call is identity bookkeeping, not an LLM message call.
|
|
25
26
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CredentialIdentityOracle.d.ts","sourceRoot":"","sources":["../../src/core/CredentialIdentityOracle.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"CredentialIdentityOracle.d.ts","sourceRoot":"","sources":["../../src/core/CredentialIdentityOracle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAK1F,sFAAsF;AACtF,MAAM,MAAM,WAAW,GAAG,CACxB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAC9D,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAE5E,MAAM,WAAW,4BAA4B;IAC3C,iGAAiG;IACjG,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,gEAAgE;IAChE,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,wBAAyB,YAAW,cAAc;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,IAAI,GAAE,4BAAiC;IAQ7C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;CA+CrE"}
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* missing-or-empty-or-nonstring email) → `unavailable` with a reason. NEVER a "mismatch": an
|
|
19
19
|
* unverifiable slot is quarantine-never-repair upstream, never a guess.
|
|
20
20
|
*
|
|
21
|
-
* Reuses `
|
|
22
|
-
* access
|
|
21
|
+
* Reuses `readClaudeOauthAsync` (OAuthRefresher) for the per-slot blob read — no hand-rolled
|
|
22
|
+
* keychain access, and NON-BLOCKING so the sequential audit loop never freezes the event loop on a
|
|
23
|
+
* slow `securityd`. The profile fetch lives here behind a bounded timeout; this file is allowlisted in
|
|
23
24
|
* `scripts/lint-no-direct-llm-http.js` for the SAME reason QuotaCollector is — an OAuth profile
|
|
24
25
|
* call is identity bookkeeping, not an LLM message call.
|
|
25
26
|
*
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
* direction: the slot is quarantined and re-probed, never guessed). The profile call here writes
|
|
31
32
|
* nothing.
|
|
32
33
|
*/
|
|
33
|
-
import {
|
|
34
|
+
import { readClaudeOauthAsync } from './OAuthRefresher.js';
|
|
34
35
|
const OAUTH_PROFILE_URL = 'https://api.anthropic.com/api/oauth/profile';
|
|
35
36
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
36
37
|
export class CredentialIdentityOracle {
|
|
@@ -46,7 +47,12 @@ export class CredentialIdentityOracle {
|
|
|
46
47
|
}
|
|
47
48
|
async resolveSlotTenant(slot) {
|
|
48
49
|
// 1. Read the slot's current credential blob → access token. (No write; reuses OAuthRefresher.)
|
|
49
|
-
|
|
50
|
+
// ASYNC read so this oracle — called sequentially over all 5 account slots by the
|
|
51
|
+
// credential-audit loop — keeps the keychain spawn OFF the event loop (each await yields),
|
|
52
|
+
// instead of the old sync read that froze the loop under multi-agent securityd contention.
|
|
53
|
+
const oauth = this.store
|
|
54
|
+
? await readClaudeOauthAsync(slot, this.store)
|
|
55
|
+
: await readClaudeOauthAsync(slot);
|
|
50
56
|
const token = oauth?.accessToken;
|
|
51
57
|
if (!token || typeof token !== 'string') {
|
|
52
58
|
return { unavailable: true, reason: 'no access token in slot credential store' };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CredentialIdentityOracle.js","sourceRoot":"","sources":["../../src/core/CredentialIdentityOracle.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"CredentialIdentityOracle.js","sourceRoot":"","sources":["../../src/core/CredentialIdentityOracle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,oBAAoB,EAAwB,MAAM,qBAAqB,CAAC;AAGjF,MAAM,iBAAiB,GAAG,6CAA6C,CAAC;AACxE,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAiBlC,MAAM,OAAO,wBAAwB;IAClB,KAAK,CAAmB;IACxB,SAAS,CAAc;IACvB,SAAS,CAAS;IAEnC,YAAY,OAAqC,EAAE;QACjD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,SAAS;YACZ,IAAI,CAAC,SAAS;gBACd,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,IAAmB,CAAuC,CAAC,CAAC;QACzF,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,IAAY;QAClC,gGAAgG;QAChG,qFAAqF;QACrF,8FAA8F;QAC9F,8FAA8F;QAC9F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK;YACtB,CAAC,CAAC,MAAM,oBAAoB,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;YAC9C,CAAC,CAAC,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,EAAE,WAAW,CAAC;QACjC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,0CAA0C,EAAE,CAAC;QACnF,CAAC;QAED,2FAA2F;QAC3F,IAAI,IAAmE,CAAC;QACxE,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE;gBAC7C,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,KAAK,EAAE;oBAChC,MAAM,EAAE,kBAAkB;oBAC1B,cAAc,EAAE,kBAAkB;oBAClC,gBAAgB,EAAE,kBAAkB;iBACrC;gBACD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,wBAAyB,GAAa,EAAE,OAAO,IAAI,SAAS,EAAE,EAAE,CAAC;QACvG,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,gFAAgF;YAChF,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,6BAA6B,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QACnF,CAAC;QAED,uCAAuC;QACvC,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,8BAA8B,EAAE,CAAC;QACvE,CAAC;QACD,MAAM,KAAK,GAAI,IAA0C,EAAE,OAAO,EAAE,KAAK,CAAC;QAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,0CAA0C,EAAE,CAAC;QACnF,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -71,6 +71,23 @@ export interface ClaudeOauth {
|
|
|
71
71
|
export interface CredentialStore {
|
|
72
72
|
read(configHome: string): string | null;
|
|
73
73
|
write(configHome: string, rawJson: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Optional NON-BLOCKING read. When present, callers on the event-loop hot path (e.g. the
|
|
76
|
+
* sequential credential-audit loop) should prefer this so a slow/contended `securityd` keychain
|
|
77
|
+
* read yields the event loop instead of freezing it. Optional so existing mocks that only
|
|
78
|
+
* implement the sync `read` keep compiling; `readClaudeOauthAsync` falls back to `read` when a
|
|
79
|
+
* store omits it.
|
|
80
|
+
*/
|
|
81
|
+
readAsync?(configHome: string): Promise<string | null>;
|
|
82
|
+
/**
|
|
83
|
+
* Optional NON-BLOCKING write. Mirror of `readAsync` for the write half of the refresh
|
|
84
|
+
* read-merge-write: a slow/contended `securityd` keychain WRITE (`add-generic-password`) is an
|
|
85
|
+
* out-of-process spawn that, run synchronously, blocked the event loop just like the read — and
|
|
86
|
+
* the refresh path issues a read AND a write per cycle. When present, `refreshClaudeToken` prefers
|
|
87
|
+
* this so the write yields the loop instead of freezing it. Optional so existing mocks that only
|
|
88
|
+
* implement the sync `write` keep compiling; the refresher falls back to `write` when omitted.
|
|
89
|
+
*/
|
|
90
|
+
writeAsync?(configHome: string, rawJson: string): Promise<boolean>;
|
|
74
91
|
}
|
|
75
92
|
/** POST-capable fetch surface (distinct from QuotaPoller's GET-only FetchImpl). */
|
|
76
93
|
export type RefreshFetch = (url: string, init: {
|
|
@@ -119,6 +136,14 @@ export declare function claudeCredentialFilePath(configHome: string): string;
|
|
|
119
136
|
export declare const defaultCredentialStore: CredentialStore;
|
|
120
137
|
/** Parse the `claudeAiOauth` block out of a config home's credential store. */
|
|
121
138
|
export declare function readClaudeOauth(configHome: string, store?: CredentialStore): ClaudeOauth | null;
|
|
139
|
+
/**
|
|
140
|
+
* NON-BLOCKING mirror of `readClaudeOauth`: identical parse logic, but reads the raw blob via the
|
|
141
|
+
* store's optional `readAsync` (off the event loop) when available, falling back to the sync `read`
|
|
142
|
+
* for any store that doesn't implement it (backward-compatible). This is the read callers on the
|
|
143
|
+
* event-loop hot path (e.g. the sequential credential-audit loop) should use so a slow/contended
|
|
144
|
+
* `securityd` keychain read yields the loop instead of freezing it.
|
|
145
|
+
*/
|
|
146
|
+
export declare function readClaudeOauthAsync(configHome: string, store?: CredentialStore): Promise<ClaudeOauth | null>;
|
|
122
147
|
/**
|
|
123
148
|
* Refresh a config home's Claude Code access token from its stored refresh token.
|
|
124
149
|
* Returns the new access token + expiry on success; otherwise a typed failure
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthRefresher.d.ts","sourceRoot":"","sources":["../../src/core/OAuthRefresher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;
|
|
1
|
+
{"version":3,"file":"OAuthRefresher.d.ts","sourceRoot":"","sources":["../../src/core/OAuthRefresher.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,12 +40,23 @@
|
|
|
40
40
|
* injectable, so the whole refresh runs hermetically with zero keychain, zero
|
|
41
41
|
* network, and a deterministic clock in tests.
|
|
42
42
|
*/
|
|
43
|
-
import { execFileSync } from 'node:child_process';
|
|
43
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
44
44
|
import crypto from 'node:crypto';
|
|
45
45
|
import fs from 'node:fs';
|
|
46
46
|
import os from 'node:os';
|
|
47
47
|
import path from 'node:path';
|
|
48
|
+
import { promisify } from 'node:util';
|
|
48
49
|
import { credentialWriteFunnel } from './CredentialWriteFunnel.js';
|
|
50
|
+
/** Promisified async exec for the non-blocking keychain read (mirrors the sync `security` spawn). */
|
|
51
|
+
const execFileAsync = promisify(execFile);
|
|
52
|
+
/**
|
|
53
|
+
* Bounds any `security` keychain spawn so a slow/contended `securityd` can never freeze the event
|
|
54
|
+
* loop indefinitely. The macOS keychain read/write is an out-of-process spawn; under multi-agent
|
|
55
|
+
* `securityd` contention an un-timeout'd SYNC spawn blocked the event loop 4–13s every cycle
|
|
56
|
+
* (the dashboard-flap / false-sleep incident). 3s is generous for a healthy keychain and a hard
|
|
57
|
+
* ceiling for a wedged one. The sibling CredentialProvider keychain spawns already set a timeout.
|
|
58
|
+
*/
|
|
59
|
+
const KEYCHAIN_TIMEOUT_MS = 3000;
|
|
49
60
|
/** Public Claude Code OAuth token endpoint (from the official client binary). */
|
|
50
61
|
export const CLAUDE_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
|
|
51
62
|
/** Public Claude Code OAuth client id (from the official client binary). */
|
|
@@ -91,11 +102,11 @@ export const defaultCredentialStore = {
|
|
|
91
102
|
read(configHome) {
|
|
92
103
|
if (process.platform === 'darwin') {
|
|
93
104
|
try {
|
|
94
|
-
const raw = execFileSync('security', ['find-generic-password', '-s', claudeCredentialService(configHome), '-w'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
105
|
+
const raw = execFileSync('security', ['find-generic-password', '-s', claudeCredentialService(configHome), '-w'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], timeout: KEYCHAIN_TIMEOUT_MS }).trim();
|
|
95
106
|
return raw || null;
|
|
96
107
|
}
|
|
97
108
|
catch {
|
|
98
|
-
return null; // @silent-fallback-ok: no keychain entry → unreadable
|
|
109
|
+
return null; // @silent-fallback-ok: no keychain entry / timeout → unreadable (caller retries → needs-reauth)
|
|
99
110
|
}
|
|
100
111
|
}
|
|
101
112
|
try {
|
|
@@ -106,6 +117,34 @@ export const defaultCredentialStore = {
|
|
|
106
117
|
return null; // @silent-fallback-ok: missing/unreadable creds file
|
|
107
118
|
}
|
|
108
119
|
},
|
|
120
|
+
/**
|
|
121
|
+
* NON-BLOCKING read: the same lookup as `read` but off the event loop. On darwin it uses the
|
|
122
|
+
* PROMISIFIED async `execFile` (so a slow `securityd` yields instead of freezing the loop); on
|
|
123
|
+
* non-darwin it uses `fs.promises.readFile`. Same `find-generic-password` args + same 3s timeout
|
|
124
|
+
* + same null-on-error semantics as the sync read.
|
|
125
|
+
*/
|
|
126
|
+
async readAsync(configHome) {
|
|
127
|
+
if (process.platform === 'darwin') {
|
|
128
|
+
try {
|
|
129
|
+
// Promisified `execFile` captures stdout/stderr into buffers (no `stdio` option — stderr is
|
|
130
|
+
// captured then ignored, matching the sync read's stderr suppression). encoding:'utf-8'
|
|
131
|
+
// gives a string stdout.
|
|
132
|
+
const { stdout } = await execFileAsync('security', ['find-generic-password', '-s', claudeCredentialService(configHome), '-w'], { encoding: 'utf-8', timeout: KEYCHAIN_TIMEOUT_MS });
|
|
133
|
+
const raw = stdout.trim();
|
|
134
|
+
return raw || null;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null; // @silent-fallback-ok: no keychain entry / timeout → unreadable (caller retries → needs-reauth)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const p = claudeCredentialFilePath(configHome);
|
|
142
|
+
return await fs.promises.readFile(p, 'utf-8');
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null; // @silent-fallback-ok: missing/unreadable creds file
|
|
146
|
+
}
|
|
147
|
+
},
|
|
109
148
|
write(configHome, rawJson) {
|
|
110
149
|
if (process.platform === 'darwin') {
|
|
111
150
|
try {
|
|
@@ -118,11 +157,11 @@ export const defaultCredentialStore = {
|
|
|
118
157
|
claudeCredentialService(configHome),
|
|
119
158
|
'-w',
|
|
120
159
|
rawJson,
|
|
121
|
-
], { stdio: ['ignore', 'ignore', 'ignore'] });
|
|
160
|
+
], { stdio: ['ignore', 'ignore', 'ignore'], timeout: KEYCHAIN_TIMEOUT_MS });
|
|
122
161
|
return true;
|
|
123
162
|
}
|
|
124
163
|
catch {
|
|
125
|
-
return false; // @silent-fallback-ok: keychain write failed → caller falls to needs-reauth
|
|
164
|
+
return false; // @silent-fallback-ok: keychain write failed / timeout → caller falls to needs-reauth
|
|
126
165
|
}
|
|
127
166
|
}
|
|
128
167
|
try {
|
|
@@ -135,6 +174,41 @@ export const defaultCredentialStore = {
|
|
|
135
174
|
return false; // @silent-fallback-ok: file write failed
|
|
136
175
|
}
|
|
137
176
|
},
|
|
177
|
+
/**
|
|
178
|
+
* NON-BLOCKING write: the same `add-generic-password` keychain write as `write` but off the event
|
|
179
|
+
* loop (PROMISIFIED `execFile` on darwin, `fs.promises` on non-darwin). Same args, same 3s timeout,
|
|
180
|
+
* same false-on-error semantics. Lets the refresh write yield a slow `securityd` instead of freezing
|
|
181
|
+
* the loop — the write half of the read-merge-write the QuotaPoller's refresher runs per cycle.
|
|
182
|
+
*/
|
|
183
|
+
async writeAsync(configHome, rawJson) {
|
|
184
|
+
if (process.platform === 'darwin') {
|
|
185
|
+
try {
|
|
186
|
+
await execFileAsync('security', [
|
|
187
|
+
'add-generic-password',
|
|
188
|
+
'-U', // update the existing entry in place
|
|
189
|
+
'-a',
|
|
190
|
+
os.userInfo().username,
|
|
191
|
+
'-s',
|
|
192
|
+
claudeCredentialService(configHome),
|
|
193
|
+
'-w',
|
|
194
|
+
rawJson,
|
|
195
|
+
], { timeout: KEYCHAIN_TIMEOUT_MS });
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false; // @silent-fallback-ok: keychain write failed / timeout → caller falls to needs-reauth
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const p = claudeCredentialFilePath(configHome);
|
|
204
|
+
await fs.promises.mkdir(path.dirname(p), { recursive: true });
|
|
205
|
+
await fs.promises.writeFile(p, rawJson, { mode: 0o600 });
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return false; // @silent-fallback-ok: file write failed
|
|
210
|
+
}
|
|
211
|
+
},
|
|
138
212
|
};
|
|
139
213
|
/** Parse the `claudeAiOauth` block out of a config home's credential store. */
|
|
140
214
|
export function readClaudeOauth(configHome, store = defaultCredentialStore) {
|
|
@@ -150,6 +224,26 @@ export function readClaudeOauth(configHome, store = defaultCredentialStore) {
|
|
|
150
224
|
return null; // @silent-fallback-ok: unparseable entry
|
|
151
225
|
}
|
|
152
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* NON-BLOCKING mirror of `readClaudeOauth`: identical parse logic, but reads the raw blob via the
|
|
229
|
+
* store's optional `readAsync` (off the event loop) when available, falling back to the sync `read`
|
|
230
|
+
* for any store that doesn't implement it (backward-compatible). This is the read callers on the
|
|
231
|
+
* event-loop hot path (e.g. the sequential credential-audit loop) should use so a slow/contended
|
|
232
|
+
* `securityd` keychain read yields the loop instead of freezing it.
|
|
233
|
+
*/
|
|
234
|
+
export async function readClaudeOauthAsync(configHome, store = defaultCredentialStore) {
|
|
235
|
+
const raw = store.readAsync ? await store.readAsync(configHome) : store.read(configHome);
|
|
236
|
+
if (!raw)
|
|
237
|
+
return null;
|
|
238
|
+
try {
|
|
239
|
+
const parsed = JSON.parse(raw);
|
|
240
|
+
const oauth = parsed?.claudeAiOauth;
|
|
241
|
+
return oauth && typeof oauth === 'object' ? oauth : null;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return null; // @silent-fallback-ok: unparseable entry
|
|
245
|
+
}
|
|
246
|
+
}
|
|
153
247
|
/**
|
|
154
248
|
* Refresh a config home's Claude Code access token from its stored refresh token.
|
|
155
249
|
* Returns the new access token + expiry on success; otherwise a typed failure
|
|
@@ -164,7 +258,10 @@ export async function refreshClaudeToken(configHome, deps = {}) {
|
|
|
164
258
|
const tokenUrl = deps.tokenUrl ?? CLAUDE_TOKEN_URL;
|
|
165
259
|
const clientId = deps.clientId ?? CLAUDE_CODE_CLIENT_ID;
|
|
166
260
|
const funnel = deps.funnel ?? credentialWriteFunnel;
|
|
167
|
-
|
|
261
|
+
// NON-BLOCKING read: prefer the store's async keychain read so a slow/contended `securityd`
|
|
262
|
+
// yields the event loop instead of freezing it (this runs per-account on the QuotaPoller timer).
|
|
263
|
+
// Falls back to the sync `read` for any store that doesn't implement `readAsync` (test mocks).
|
|
264
|
+
const raw = store.readAsync ? await store.readAsync(configHome) : store.read(configHome);
|
|
168
265
|
if (!raw)
|
|
169
266
|
return { ok: false, reason: 'read-failed' };
|
|
170
267
|
let parsed;
|
|
@@ -232,7 +329,13 @@ export async function refreshClaudeToken(configHome, deps = {}) {
|
|
|
232
329
|
// NOT a corruption — it is "busy, retry": surface 'write-skipped' so the QuotaPoller treats it
|
|
233
330
|
// as no-snapshot-this-cycle, NEVER needs-reauth. The exchange already succeeded; the existing
|
|
234
331
|
// (still-valid) credential is untouched.
|
|
235
|
-
|
|
332
|
+
// NON-BLOCKING write: prefer the store's async keychain write (off the event loop) so the write
|
|
333
|
+
// half of this read-merge-write yields a slow `securityd` instead of freezing the loop. Falls back
|
|
334
|
+
// to the sync `write` for any store that doesn't implement `writeAsync` (test mocks). The funnel's
|
|
335
|
+
// per-slot lock already serializes against a concurrent swap/refresh on the SAME slot regardless.
|
|
336
|
+
const writeOutcome = await funnel.withSlotLock(credentialSlotKey(configHome), () => store.writeAsync
|
|
337
|
+
? store.writeAsync(configHome, JSON.stringify(updatedRaw))
|
|
338
|
+
: store.write(configHome, JSON.stringify(updatedRaw)));
|
|
236
339
|
if (!writeOutcome.ran) {
|
|
237
340
|
return { ok: false, reason: 'write-skipped' };
|
|
238
341
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthRefresher.js","sourceRoot":"","sources":["../../src/core/OAuthRefresher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"OAuthRefresher.js","sourceRoot":"","sources":["../../src/core/OAuthRefresher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAyB,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAE1F,qGAAqG;AACrG,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC,iFAAiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,4CAA4C,CAAC;AAC7E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,sCAAsC,CAAC;AAE5E,MAAM,aAAa,GAAG,YAAY,CAAC;AACnC,MAAM,cAAc,GAAG,YAAY,CAAC;AAuEpC,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAkB;IAClD,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAAkB;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;IAC5C,OAAO,IAAI,KAAK,WAAW;QACzB,CAAC,CAAC,yBAAyB;QAC3B,CAAC,CAAC,2BAA2B,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACtG,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,wBAAwB,CAAC,UAAkB;IACzD,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAC,CAAC;AAChE,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,MAAM,sBAAsB,GAAoB;IACrD,IAAI,CAAC,UAAkB;QACrB,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,YAAY,CACtB,UAAU,EACV,CAAC,uBAAuB,EAAE,IAAI,EAAE,uBAAuB,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,EAC1E,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,mBAAmB,EAAE,CACzF,CAAC,IAAI,EAAE,CAAC;gBACT,OAAO,GAAG,IAAI,IAAI,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC,CAAC,gGAAgG;YAC/G,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;YAC/C,OAAO,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,CAAC,qDAAqD;QACpE,CAAC;IACH,CAAC;IACD;;;;;OAKG;IACH,KAAK,CAAC,SAAS,CAAC,UAAkB;QAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,4FAA4F;gBAC5F,wFAAwF;gBACxF,yBAAyB;gBACzB,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CACpC,UAAU,EACV,CAAC,uBAAuB,EAAE,IAAI,EAAE,uBAAuB,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,EAC1E,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,CACpD,CAAC;gBACF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC1B,OAAO,GAAG,IAAI,IAAI,CAAC;YACrB,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC,CAAC,gGAAgG;YAC/G,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;YAC/C,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,CAAC,qDAAqD;QACpE,CAAC;IACH,CAAC;IACD,KAAK,CAAC,UAAkB,EAAE,OAAe;QACvC,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,YAAY,CACV,UAAU,EACV;oBACE,sBAAsB;oBACtB,IAAI,EAAE,qCAAqC;oBAC3C,IAAI;oBACJ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ;oBACtB,IAAI;oBACJ,uBAAuB,CAAC,UAAU,CAAC;oBACnC,IAAI;oBACJ,OAAO;iBACR,EACD,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,mBAAmB,EAAE,CACxE,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC,CAAC,sFAAsF;YACtG,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;YAC/C,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC9C,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC,CAAC,yCAAyC;QACzD,CAAC;IACH,CAAC;IACD;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CAAC,UAAkB,EAAE,OAAe;QAClD,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,aAAa,CACjB,UAAU,EACV;oBACE,sBAAsB;oBACtB,IAAI,EAAE,qCAAqC;oBAC3C,IAAI;oBACJ,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ;oBACtB,IAAI;oBACJ,uBAAuB,CAAC,UAAU,CAAC;oBACnC,IAAI;oBACJ,OAAO;iBACR,EACD,EAAE,OAAO,EAAE,mBAAmB,EAAE,CACjC,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC,CAAC,sFAAsF;YACtG,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;YAC/C,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9D,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACzD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC,CAAC,yCAAyC;QACzD,CAAC;IACH,CAAC;CACF,CAAC;AAEF,+EAA+E;AAC/E,MAAM,UAAU,eAAe,CAC7B,UAAkB,EAClB,QAAyB,sBAAsB;IAE/C,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,MAAM,KAAK,GAAG,MAAM,EAAE,aAAa,CAAC;QACpC,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAqB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,yCAAyC;IACxD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAAkB,EAClB,QAAyB,sBAAsB;IAE/C,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzF,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,MAAM,KAAK,GAAG,MAAM,EAAE,aAAa,CAAC;QACpC,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAqB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,yCAAyC;IACxD,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,UAAkB,EAClB,OAAoB,EAAE;IAEtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,sBAAsB,CAAC;IACnD,MAAM,SAAS,GACb,IAAI,CAAC,SAAS;QACd,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,IAAmB,CAAwC,CAAC,CAAC;IAC1F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,gBAAgB,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,qBAAqB,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,qBAAqB,CAAC;IAEpD,4FAA4F;IAC5F,iGAAiG;IACjG,+FAA+F;IAC/F,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzF,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACtD,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAC9C,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,IAAI,CAAuB,CAAC;IACpE,MAAM,YAAY,GAAG,KAAK,EAAE,YAAY,CAAC;IACzC,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;QACtD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACnD,CAAC;IAED,IAAI,GAAkE,CAAC;IACvE,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE;YAC9B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,UAAU,EAAE,eAAe;gBAC3B,aAAa,EAAE,YAAY;gBAC3B,SAAS,EAAE,QAAQ;aACpB,CAAC;SACH,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAClD,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAEjF,IAAI,IAA6B,CAAC;IAClC,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACrD,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,EAAE,YAAY,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,EAAE,UAAU,CAAC;IACnC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC1E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IACrD,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,EAAE,aAAa,CAAC;IACvC,MAAM,OAAO,GACX,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,CAAC,UAAU,CAAC,cAAc,CAAC;QACrC,UAAU,KAAK,YAAY,CAAC;IAE9B,MAAM,SAAS,GAAG,GAAG,EAAE,GAAG,SAAS,GAAG,IAAI,CAAC;IAC3C,6EAA6E;IAC7E,oEAAoE;IACpE,MAAM,YAAY,GAAgB;QAChC,GAAG,KAAK;QACR,WAAW,EAAE,SAAS;QACtB,YAAY,EACV,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,UAAU,CAAC,cAAc,CAAC;YACrE,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,YAAY;QAClB,SAAS;KACV,CAAC;IACF,MAAM,UAAU,GAAG,EAAE,GAAG,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;IAE9D,uFAAuF;IACvF,2FAA2F;IAC3F,+FAA+F;IAC/F,8FAA8F;IAC9F,yCAAyC;IACzC,gGAAgG;IAChG,mGAAmG;IACnG,mGAAmG;IACnG,kGAAkG;IAClG,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CACjF,KAAK,CAAC,UAAU;QACd,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC1D,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CACxD,CAAC;IACF,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAChD,CAAC;IACD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAC/C,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAClE,CAAC"}
|
|
@@ -34,8 +34,14 @@
|
|
|
34
34
|
import type { SubscriptionPool, SubscriptionAccount, AccountQuotaSnapshot } from './SubscriptionPool.js';
|
|
35
35
|
import { type RefreshResult } from './OAuthRefresher.js';
|
|
36
36
|
import type { CredentialLocationGate } from './CredentialLocationGate.js';
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Injectable token resolver — returns an account's OAuth access token or null.
|
|
39
|
+
* The default (`defaultTokenResolver`) is ASYNC so the per-account keychain read happens OFF the
|
|
40
|
+
* event loop (a slow/contended `securityd` read used to freeze the loop every poll cycle — the
|
|
41
|
+
* dashboard-flap / false-sleep residual). `pollAccount` `await`s the result, so a SYNC resolver
|
|
42
|
+
* (e.g. a test stub returning a plain string) is equally valid — hence the union return type.
|
|
43
|
+
*/
|
|
44
|
+
export type TokenResolver = (account: SubscriptionAccount) => string | null | Promise<string | null>;
|
|
39
45
|
/**
|
|
40
46
|
* Injectable account refresher — exchanges a config home's stored refresh token
|
|
41
47
|
* for a fresh access token (see OAuthRefresher). Defaults to the real keychain/
|
|
@@ -95,7 +101,7 @@ export interface BurnRate {
|
|
|
95
101
|
* is still returned here (it's a valid string) — expiry is detected by the usage
|
|
96
102
|
* read's 401 and recovered by the refresher, not by this resolver.
|
|
97
103
|
*/
|
|
98
|
-
export declare function defaultTokenResolver(account: SubscriptionAccount): string | null
|
|
104
|
+
export declare function defaultTokenResolver(account: SubscriptionAccount): Promise<string | null>;
|
|
99
105
|
/**
|
|
100
106
|
* Read the account email (`oauthAccount.emailAddress`) Claude Code records for a
|
|
101
107
|
* config home. This is a PUBLIC account identifier (not a secret) — it lets the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaPoller.d.ts","sourceRoot":"","sources":["../../src/core/QuotaPoller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E
|
|
1
|
+
{"version":3,"file":"QuotaPoller.d.ts","sourceRoot":"","sources":["../../src/core/QuotaPoller.ts"],"names":[],"mappings":"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"}
|
package/dist/core/QuotaPoller.js
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import fs from 'node:fs';
|
|
35
35
|
import path from 'node:path';
|
|
36
|
-
import {
|
|
36
|
+
import { readClaudeOauthAsync, refreshClaudeToken, expandHome, } from './OAuthRefresher.js';
|
|
37
37
|
const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
38
38
|
/**
|
|
39
39
|
* Resolve a claude-code account's OAuth access token from its config home,
|
|
@@ -44,11 +44,15 @@ const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
|
44
44
|
* is still returned here (it's a valid string) — expiry is detected by the usage
|
|
45
45
|
* read's 401 and recovered by the refresher, not by this resolver.
|
|
46
46
|
*/
|
|
47
|
-
export function defaultTokenResolver(account) {
|
|
47
|
+
export async function defaultTokenResolver(account) {
|
|
48
48
|
if (account.provider !== 'anthropic' || account.framework !== 'claude-code') {
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
// Single periodic keychain read per poll cycle, bounded by OAuthRefresher's 3s timeout AND run
|
|
52
|
+
// OFF the event loop via `readClaudeOauthAsync` (promisified `security` spawn). The earlier sync
|
|
53
|
+
// read blocked the loop for the full spawn duration each cycle — under multi-agent `securityd`
|
|
54
|
+
// contention that was seconds, and across N accounts a burst (the residual freeze this fixes).
|
|
55
|
+
const oauth = await readClaudeOauthAsync(account.configHome);
|
|
52
56
|
const tok = oauth?.accessToken;
|
|
53
57
|
return typeof tok === 'string' && tok.startsWith('sk-ant-oat') ? tok : null;
|
|
54
58
|
}
|
|
@@ -237,7 +241,7 @@ export class QuotaPoller {
|
|
|
237
241
|
// so a swap mid-poll can't read/refresh/flag the wrong tenant. account.id is preserved (only
|
|
238
242
|
// the slot home moves), so pool.update + logging still name the right account.
|
|
239
243
|
const slotAccount = this.accountForReads(account);
|
|
240
|
-
const token = this.tokenResolver(slotAccount);
|
|
244
|
+
const token = await this.tokenResolver(slotAccount);
|
|
241
245
|
if (!token) {
|
|
242
246
|
this.logger.warn(`[QuotaPoller] no resolvable token for account ${account.id} — skipping`);
|
|
243
247
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaPoller.js","sourceRoot":"","sources":["../../src/core/QuotaPoller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAM7B,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"QuotaPoller.js","sourceRoot":"","sources":["../../src/core/QuotaPoller.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CredentialProvider.d.ts","sourceRoot":"","sources":["../../src/monitoring/CredentialProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"CredentialProvider.d.ts","sourceRoot":"","sources":["../../src/monitoring/CredentialProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Part of the Instar Quota Migration spec (Phase 1).
|
|
9
9
|
*/
|
|
10
|
-
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
|
+
import { promisify } from 'node:util';
|
|
14
15
|
import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
|
|
15
16
|
import { credentialWriteFunnel, } from '../core/CredentialWriteFunnel.js';
|
|
16
17
|
import { credentialSlotKey } from '../core/OAuthRefresher.js';
|
|
@@ -38,6 +39,14 @@ export function redactEmail(email) {
|
|
|
38
39
|
}
|
|
39
40
|
// ── Keychain Provider (macOS) ───────────────────────────────────────
|
|
40
41
|
const KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
42
|
+
/**
|
|
43
|
+
* Promisified async exec for the NON-BLOCKING keychain read. The macOS keychain read is an
|
|
44
|
+
* out-of-process `security` spawn; run SYNCHRONOUSLY (`execFileSync`) it blocks the event loop for
|
|
45
|
+
* the whole spawn — under multi-agent `securityd` contention that was seconds every QuotaCollector
|
|
46
|
+
* poll cycle (the dashboard-flap / false-sleep residual). `readCredentials` (the polled read on the
|
|
47
|
+
* collection-cycle timer) uses this so the read yields the loop instead of freezing it.
|
|
48
|
+
*/
|
|
49
|
+
const execFileAsync = promisify(execFile);
|
|
41
50
|
export class KeychainCredentialProvider {
|
|
42
51
|
platform = 'darwin';
|
|
43
52
|
securityLevel = 'os-encrypted';
|
|
@@ -47,8 +56,19 @@ export class KeychainCredentialProvider {
|
|
|
47
56
|
}
|
|
48
57
|
async readCredentials() {
|
|
49
58
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
// NON-BLOCKING keychain read (promisified `execFile`) so a slow/contended `securityd` yields
|
|
60
|
+
// the event loop instead of freezing it. `readCredentials` is already async (callers await it);
|
|
61
|
+
// the prior `execFileSync` made the `async` a lie — it blocked the loop for the spawn duration
|
|
62
|
+
// on the QuotaCollector poll timer. Same args + same 10s timeout + same null-on-error semantics.
|
|
63
|
+
const { stdout } = await execFileAsync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], { encoding: 'utf-8', timeout: 10000 });
|
|
64
|
+
// RULE 3: EXEMPT — this parses our OWN credential store (the `Claude Code-credentials`
|
|
65
|
+
// keychain entry the agent itself owns, fixed `claudeAiOauth` schema), NOT an evolving
|
|
66
|
+
// upstream UI/log we detect state from. It is fail-SAFE (any parse error → the `catch`
|
|
67
|
+
// returns null → caller treats it as no-creds/needs-reauth) and cross-checked downstream
|
|
68
|
+
// (a stale/wrong token surfaces as a 401 → needs-reauth, never silent corruption). The
|
|
69
|
+
// async conversion is behavior-preserving; the parse itself is unchanged from the prior
|
|
70
|
+
// (already-merged) sync read.
|
|
71
|
+
const data = JSON.parse(stdout.trim());
|
|
52
72
|
const oauth = data.claudeAiOauth;
|
|
53
73
|
if (!oauth?.accessToken)
|
|
54
74
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CredentialProvider.js","sourceRoot":"","sources":["../../src/monitoring/CredentialProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"CredentialProvider.js","sourceRoot":"","sources":["../../src/monitoring/CredentialProvider.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-22T18:50:01.119Z",
|
|
5
|
+
"instarVersion": "1.3.645",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The macOS keychain credential read (`OAuthRefresher.defaultCredentialStore.read`) was a synchronous
|
|
9
|
+
`execFileSync('security', …)` with no timeout, run on the server's single event loop. The
|
|
10
|
+
credential-audit loop reads all of an agent's claude account slots sequentially through it, so when
|
|
11
|
+
several co-resident agents contend on one `securityd`, each read stalled seconds and the loop froze
|
|
12
|
+
4–13s every ~30–65s — dropping the dashboard websocket (the "Disconnected" flapping) and false-firing
|
|
13
|
+
the SleepWakeDetector (a ~0-CPU I/O-wait block the CPU check can't see). This is the SECOND
|
|
14
|
+
event-loop blocker behind the dashboard flapping; the tmux Event-Loop Resilience fix (v1.3.643)
|
|
15
|
+
removed the first (the tmux calls), this removes the keychain one.
|
|
16
|
+
|
|
17
|
+
The read now has an async, off-loop variant (`readAsync`, promisified `execFile`, 3s timeout) used on
|
|
18
|
+
the audit hot path (`CredentialIdentityOracle.resolveSlotTenant` awaits it, so each slot read yields
|
|
19
|
+
the loop), plus a `timeout: 3000` on the remaining sync read/write as a bound for any non-hot-path
|
|
20
|
+
caller. The sibling `CredentialProvider.ts` already set a keychain timeout; OAuthRefresher was missed.
|
|
21
|
+
|
|
22
|
+
## What to Tell Your User
|
|
23
|
+
|
|
24
|
+
If your dashboard kept showing "Disconnected" even after the tmux fix, this is the rest of the cause:
|
|
25
|
+
a slow shared macOS keychain was freezing the server loop. After this update the dashboard stays
|
|
26
|
+
connected under multi-agent load, and the agent stops occasionally misreporting itself as having gone
|
|
27
|
+
to sleep.
|
|
28
|
+
|
|
29
|
+
## Summary of New Capabilities
|
|
30
|
+
|
|
31
|
+
- Async, timeout-bounded keychain credential read (`CredentialStore.readAsync`, optional + backward
|
|
32
|
+
compatible); the credential-audit hot path reads off the event loop.
|
|
33
|
+
- A 3s timeout on the remaining synchronous keychain read/write — an unbounded `securityd` stall can
|
|
34
|
+
no longer wedge the loop (a timeout maps to needs-reauth, retried next cycle).
|
|
35
|
+
- Removes the second event-loop blocker behind the dashboard "disconnected" flapping + the
|
|
36
|
+
SleepWakeDetector false-wakes.
|
|
37
|
+
|
|
38
|
+
## Evidence
|
|
39
|
+
|
|
40
|
+
Root cause diagnosed by a live `/usr/bin/sample` of the running server during a freeze (1567/1567
|
|
41
|
+
main-thread samples in `SyncProcessRunner::Spawn → kevent` with `com.apple.security` loaded) +
|
|
42
|
+
correlation probes catching freezes ~64s apart. Fix covered by a new async-read test suite (parity
|
|
43
|
+
with the sync read, fallback-to-sync for stores without `readAsync`, and a deferred-promise test
|
|
44
|
+
proving `resolveSlotTenant` awaits the async path); 83/83 tests across the credential suites green,
|
|
45
|
+
tsc clean, no-silent-fallbacks ratchet + sync-subprocess chokepoint lint green.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The first keychain fix (PR #1248) took the credential-AUDIT keychain read off the event loop. A live
|
|
9
|
+
`/usr/bin/sample` of the running server then found THREE MORE timer-driven synchronous macOS keychain
|
|
10
|
+
reads still blocking the loop — all on the QuotaManager / QuotaPoller poll path, which runs every ~60s
|
|
11
|
+
(and as often as ~10s at the critical quota tier — the source of the longer freezes). This converts
|
|
12
|
+
all three to async (promisified `execFile`), completing the keychain class:
|
|
13
|
+
|
|
14
|
+
- `KeychainCredentialProvider.readCredentials` — was `execFileSync` on an already-`async` method (the
|
|
15
|
+
sync call made the `async` a lie); now `await execFileAsync`, same args + same 10s timeout.
|
|
16
|
+
- `QuotaPoller.defaultTokenResolver` — the per-account keychain read in `pollAll`; now `await`s the
|
|
17
|
+
async read the first fix added. The `TokenResolver` type widened to accept a `Promise` so a sync test
|
|
18
|
+
stub stays valid.
|
|
19
|
+
- `OAuthRefresher.refreshClaudeToken` — the 401-path read-merge-write now prefers the async read AND a
|
|
20
|
+
new optional `writeAsync` (promisified `add-generic-password`, same 3s timeout). Both fall back to
|
|
21
|
+
the sync `read`/`write` for any store that doesn't implement the async variant.
|
|
22
|
+
|
|
23
|
+
Behavior-preserving (same args, timeouts, and null/false-on-error semantics); 258 tests green.
|
|
24
|
+
|
|
25
|
+
## What to Tell Your User
|
|
26
|
+
|
|
27
|
+
If your dashboard still flapped "Disconnected" or the agent still occasionally misreported itself as
|
|
28
|
+
asleep after the first keychain fix, this is the rest of the cause: the periodic quota-poll keychain
|
|
29
|
+
reads were still freezing the server loop. After this update those run off the loop too, so the
|
|
30
|
+
dashboard stays connected under multi-agent keychain contention. Note honestly: there is ALSO a
|
|
31
|
+
separate, non-keychain freeze still being fixed (a large-JSON file read) — not part of this change.
|
|
32
|
+
|
|
33
|
+
## Summary of New Capabilities
|
|
34
|
+
|
|
35
|
+
- The QuotaManager collection-cycle keychain read (`KeychainCredentialProvider.readCredentials`) runs
|
|
36
|
+
off the event loop (the sync call on an already-`async` method is gone).
|
|
37
|
+
- The QuotaPoller per-account token resolver reads the keychain off the loop; the refresh path's
|
|
38
|
+
read-merge-write reads AND writes off the loop via the existing async read + a new optional
|
|
39
|
+
`writeAsync` keychain write.
|
|
40
|
+
- Completes the keychain class of the dashboard "disconnected" flapping + SleepWakeDetector false-wakes
|
|
41
|
+
begun by the tmux Event-Loop Resilience fix (v1.3.643) and the first keychain fix (PR #1248).
|
|
42
|
+
|
|
43
|
+
## Evidence
|
|
44
|
+
|
|
45
|
+
Root cause diagnosed by a live `/usr/bin/sample` of the running server during a freeze (main-thread
|
|
46
|
+
samples in the `security` keychain spawn on the quota-poll timer). Covered by a new
|
|
47
|
+
`credential-provider-async-read` test suite plus extended `oauth-refresher-async-read` and updated
|
|
48
|
+
`quota-poller` suites (28 tests in the three targeted files, parity with the sync paths + fallback to
|
|
49
|
+
sync for stores without the async variant); 258 tests green across the affected suites, tsc clean,
|
|
50
|
+
no-silent-fallbacks ratchet + sync-subprocess chokepoint lint green.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Credential keychain read — async + timeout-bounded — ELI16
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
Your agent's server has a single main thread that handles everything — answering the dashboard,
|
|
6
|
+
replying to messages, running every background check. If any one operation on that thread stops to
|
|
7
|
+
wait for something slow, *everything* waits. That's a "freeze."
|
|
8
|
+
|
|
9
|
+
This fixes a freeze. To know which account a credential belongs to, the server reads it from the
|
|
10
|
+
macOS keychain by running the `security` command — and it did that the BLOCKING way (`execFileSync`)
|
|
11
|
+
with no time limit. Normally a keychain read is instant. But you run several agents on one Mac, and
|
|
12
|
+
they all go through one macOS keychain service (`securityd`); when they pile up, each read can take
|
|
13
|
+
several seconds. The server checks all 5 of your Claude accounts one after another, so the freezes
|
|
14
|
+
added up to **4–13 seconds, roughly every 30–60 seconds**.
|
|
15
|
+
|
|
16
|
+
That's what was actually still breaking the dashboard. The earlier tmux fix (v1.3.643) correctly
|
|
17
|
+
removed the *tmux* version of this same problem, but there were TWO blocking calls, and this is the
|
|
18
|
+
second one. During each freeze the dashboard's live connection drops (you saw "Disconnected"), and
|
|
19
|
+
the freeze even looked to the agent like the laptop had gone to sleep, so it kept false-alarming a
|
|
20
|
+
"wake."
|
|
21
|
+
|
|
22
|
+
## What already exists
|
|
23
|
+
|
|
24
|
+
The agent already has the right pattern in a sibling file (`CredentialProvider.ts`) — it reads the
|
|
25
|
+
keychain with a 10-second time limit. This read path just never got that treatment. And the
|
|
26
|
+
credential-audit loop that triggers the reads is already `async`, so switching it to a non-blocking
|
|
27
|
+
read is clean.
|
|
28
|
+
|
|
29
|
+
## What's new
|
|
30
|
+
|
|
31
|
+
- The keychain read now has an **async, off-the-main-thread version** (`readAsync`) that the
|
|
32
|
+
audit loop uses — so while one account's keychain read is in flight, the main thread is free to
|
|
33
|
+
answer the dashboard, messages, and everything else. The freeze is gone, regardless of how slow
|
|
34
|
+
`securityd` gets or how many accounts there are.
|
|
35
|
+
- Both the old synchronous read and write now also carry a **3-second time limit**, so even a
|
|
36
|
+
caller that still reads synchronously can't wedge forever (it just falls back to "needs re-auth"
|
|
37
|
+
and retries next cycle — exactly what it did before for a missing entry).
|
|
38
|
+
- It's backward-compatible: the new async method is optional, so nothing else has to change.
|
|
39
|
+
|
|
40
|
+
## What you need to decide
|
|
41
|
+
|
|
42
|
+
Nothing. It's a self-contained, low-risk fix on one read path, fully covered by tests. Once it ships
|
|
43
|
+
and your agent updates, the dashboard should stay connected under load instead of flapping, and the
|
|
44
|
+
spurious "wake" alarms stop.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# The rest of the keychain freezes are gone
|
|
2
|
+
|
|
3
|
+
You know how your dashboard kept showing "Disconnected" and the agent sometimes thought it had gone to
|
|
4
|
+
sleep when it hadn't? We already fixed the two biggest causes: the slow tmux calls, and then the worst
|
|
5
|
+
of the keychain reads.
|
|
6
|
+
|
|
7
|
+
A "keychain read" is the agent asking the Mac's secure password store (the same vault Safari uses to
|
|
8
|
+
remember your logins) for the saved login it needs to talk to its account. On a Mac, that question goes
|
|
9
|
+
to a single system service shared by every app and every agent on the machine. When that service is busy
|
|
10
|
+
— and it gets busy when several agents ask at once — each question can take seconds to answer. The
|
|
11
|
+
problem was that the agent asked the question the BLOCKING way: it stood frozen at the counter waiting
|
|
12
|
+
for the answer instead of going off and doing other work. While frozen, it couldn't talk to your
|
|
13
|
+
dashboard, so the dashboard dropped the connection and the agent's "am I awake?" check misread the
|
|
14
|
+
stillness as the computer going to sleep.
|
|
15
|
+
|
|
16
|
+
The first fix only converted ONE place that asked the blocking question. After it shipped, we ran a live
|
|
17
|
+
profiler on the running server and caught THREE MORE places still asking the blocking way — all of them
|
|
18
|
+
on the part of the agent that checks, roughly every 60 seconds (and as often as every 10 seconds when an
|
|
19
|
+
account is running low), how much quota each of your accounts has left. Each check was reading the
|
|
20
|
+
keychain the frozen-at-the-counter way, once per account.
|
|
21
|
+
|
|
22
|
+
This update converts all three of those to ask the question the non-blocking way: the agent leaves a note
|
|
23
|
+
("tell me when you have the answer") and keeps doing its other work in the meantime, so the dashboard
|
|
24
|
+
stays connected and the false "I went to sleep" reports stop. Same answers, same timeouts, same safety
|
|
25
|
+
behavior if the keychain can't be read — just no more freezing.
|
|
26
|
+
|
|
27
|
+
One honest note: there is still a SEPARATE freeze being fixed that has nothing to do with the keychain —
|
|
28
|
+
it's caused by reading one large JSON file the blocking way. That one is being handled on its own and is
|
|
29
|
+
not part of this change. With this update, the keychain class of freezes is finished.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Side-Effects Review — Credential keychain read: async + timeout-bounded
|
|
2
|
+
|
|
3
|
+
**Slug:** `credential-keychain-async-read` · **Tier:** 1 (focused bug fix, no spec; rigorous
|
|
4
|
+
root-cause diagnosis via a live `/usr/bin/sample` of the running server). Parent principle:
|
|
5
|
+
**Structure beats Willpower** — the same "never block the event loop" guarantee the tmux
|
|
6
|
+
Event-Loop Resilience fix made structural, applied to the second blocking call site it didn't cover.
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
The macOS keychain credential read in `OAuthRefresher.ts` (`defaultCredentialStore.read`) was a
|
|
11
|
+
SYNCHRONOUS `execFileSync('security', …)` with NO timeout, on the event loop. The credential-audit
|
|
12
|
+
hot path — `CredentialLocationLedger.auditIdentities()` → loops sequentially over all 5 claude
|
|
13
|
+
account slots → `await CredentialIdentityOracle.resolveSlotTenant()` → `readClaudeOauth()` → that
|
|
14
|
+
sync `security` spawn — froze the whole event loop 4–13s every ~30–65s under multi-agent `securityd`
|
|
15
|
+
contention, dropping the dashboard websocket (user-visible flapping) and false-firing the
|
|
16
|
+
SleepWakeDetector (~0-CPU I/O-wait). This adds an async `readAsync` (promisified `execFile`,
|
|
17
|
+
3s-timeout) used on the hot path (`resolveSlotTenant` awaits it → each slot read yields the loop),
|
|
18
|
+
plus a `timeout: 3000` on the remaining sync `read`/`write` for any non-hot-path caller. The sibling
|
|
19
|
+
`monitoring/CredentialProvider.ts` already set `timeout:10000`; OAuthRefresher was simply missed.
|
|
20
|
+
|
|
21
|
+
## 1. Behavioral equivalence / correctness
|
|
22
|
+
|
|
23
|
+
`readAsync` mirrors the sync read's args + null-on-error semantics exactly; `readClaudeOauthAsync`
|
|
24
|
+
reuses the identical parse and falls back to the sync `read` for any store without `readAsync`
|
|
25
|
+
(the interface method is OPTIONAL, so all existing `CredentialStore` mocks compile unchanged). The
|
|
26
|
+
only consumer switched to the async path is `resolveSlotTenant` (already an `async` method awaited by
|
|
27
|
+
the audit loop) — verified nothing after the read assumes synchrony (only `oauth?.accessToken` is
|
|
28
|
+
used). 83/83 tests across the credential suites green, including a deferred-promise test proving
|
|
29
|
+
`resolveSlotTenant` does not resolve until the async read resolves (it genuinely awaits the async
|
|
30
|
+
path, not the sync read).
|
|
31
|
+
|
|
32
|
+
## 2. Failure modes / fail-safe
|
|
33
|
+
|
|
34
|
+
Every error path returns `null` (unreadable → caller falls to needs-reauth, retried next cycle) —
|
|
35
|
+
identical to the prior sync behavior. A 3s timeout now bounds a wedged `securityd` instead of an
|
|
36
|
+
unbounded block: a timeout maps to `null` (needs-reauth) exactly like a missing entry. The async
|
|
37
|
+
`execFile` buffers stdout/stderr (the `stdio` option is dropped on the promisified overload — stderr
|
|
38
|
+
is captured-then-ignored, matching the sync read's `stdio:['ignore','pipe','ignore']`).
|
|
39
|
+
|
|
40
|
+
## 3. Blast radius
|
|
41
|
+
|
|
42
|
+
Two files of behavior (`OAuthRefresher.ts`, `CredentialIdentityOracle.ts`) + a one-line comment in
|
|
43
|
+
`QuotaPoller.ts`. `QuotaPoller.defaultTokenResolver` stays sync by design (a single periodic read,
|
|
44
|
+
now timeout-bounded — making it async would ripple through `SubscriptionAccount` token resolution
|
|
45
|
+
for no benefit). No write-path semantics change (only the timeout bound is added). No credential
|
|
46
|
+
VALUE ever leaves the funnel; no new external surface.
|
|
47
|
+
|
|
48
|
+
## 4. Interactions
|
|
49
|
+
|
|
50
|
+
Complements the tmux Event-Loop Resilience fix (v1.3.643): that fix took the SYNC TMUX calls off the
|
|
51
|
+
loop; this takes the SYNC KEYCHAIN call off the loop. Together they remove the two periodic
|
|
52
|
+
event-loop blockers that caused the dashboard "disconnected" flapping. The async read also removes
|
|
53
|
+
the SleepWakeDetector false-wakes (no ~0-CPU block → no misread as sleep), so the spurious
|
|
54
|
+
wake-recovery cascade this triggered stops.
|
|
55
|
+
|
|
56
|
+
## 5. Rollback
|
|
57
|
+
|
|
58
|
+
Revert the 2 source files. The change is additive (a new optional interface method + an async
|
|
59
|
+
function) plus a one-line switch in `resolveSlotTenant`; the sync `read` remains the fallback, so a
|
|
60
|
+
partial revert (keeping only the `timeout: 3000` bound) is also safe.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Side-Effects Review — Keychain residual: the remaining timer-driven sync keychain reads → async
|
|
2
|
+
|
|
3
|
+
**Slug:** `keychain-residual-async-read` · **Tier:** 1 (focused low-risk bug fix, no spec; completes
|
|
4
|
+
the keychain class of the dashboard event-loop freeze that the first fix opened). Parent principle:
|
|
5
|
+
**Structure beats Willpower** — the same "never block the event loop" guarantee, applied to the three
|
|
6
|
+
remaining timer-driven sync keychain call sites the first fix (PR #1248) did not cover.
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
The first keychain fix (PR #1248, merged as `e14d8ead4`) took the credential-audit hot path's
|
|
11
|
+
synchronous `security` keychain read off the event loop. A live `/usr/bin/sample` of the running
|
|
12
|
+
server then found THREE MORE timer-driven synchronous keychain call sites still freezing the loop —
|
|
13
|
+
all on the QuotaManager / QuotaPoller poll path, which runs every ~60s (and as often as every ~10s at
|
|
14
|
+
the critical quota tier). This converts those three to async (promisified `execFile`), completing the
|
|
15
|
+
keychain class:
|
|
16
|
+
|
|
17
|
+
1. **`KeychainCredentialProvider.readCredentials`** (`src/monitoring/CredentialProvider.ts`) — was a
|
|
18
|
+
synchronous `execFileSync('security', …)` on a method already declared `async` (callers already
|
|
19
|
+
`await` it, so the sync call made the `async` a lie). Driven by the QuotaManager collection-cycle
|
|
20
|
+
timer (60s, down to 10s at the critical tier — the source of the longer freezes). Now
|
|
21
|
+
`await execFileAsync(...)`, SAME args + SAME 10s timeout + SAME null-on-error semantics.
|
|
22
|
+
2. **`QuotaPoller.defaultTokenResolver`** (`src/core/QuotaPoller.ts`) — the per-account periodic
|
|
23
|
+
keychain read inside `pollAll`. Was sync `readClaudeOauth`; now `await readClaudeOauthAsync` (the
|
|
24
|
+
async read the first fix already added). `pollAccount` now `await`s the resolver; the `TokenResolver`
|
|
25
|
+
type widened to `string | null | Promise<string | null>` so a sync test stub stays valid.
|
|
26
|
+
3. **`OAuthRefresher.refreshClaudeToken`** (`src/core/OAuthRefresher.ts`) — the read-merge-write on the
|
|
27
|
+
401 refresh path issues a keychain READ and a keychain WRITE per cycle. The read now prefers the
|
|
28
|
+
existing async `readAsync`; a NEW optional `writeAsync` (promisified `add-generic-password`, same
|
|
29
|
+
3s timeout, same false-on-error semantics) is preferred for the write. Both fall back to the sync
|
|
30
|
+
`read`/`write` for any store that doesn't implement the async variant (test mocks).
|
|
31
|
+
|
|
32
|
+
## 1. Behavioral equivalence / correctness
|
|
33
|
+
|
|
34
|
+
Each async variant mirrors its sync sibling's args, timeout, and null/false-on-error semantics
|
|
35
|
+
exactly. `readCredentials` keeps its identical JSON parse (now over `stdout`). `defaultTokenResolver`
|
|
36
|
+
keeps the identical `sk-ant-oat` prefix check. `refreshClaudeToken`'s async read/write are gated on
|
|
37
|
+
the OPTIONAL interface methods (`store.readAsync ? … : store.read`, `store.writeAsync ? … : store.write`),
|
|
38
|
+
so every existing `CredentialStore` mock that implements only the sync methods compiles AND runs
|
|
39
|
+
unchanged. 258 tests across the affected suites green (28 in the three targeted files), tsc clean,
|
|
40
|
+
lints clean.
|
|
41
|
+
|
|
42
|
+
## 2. Failure modes / fail-safe
|
|
43
|
+
|
|
44
|
+
Identical to the sync paths. A keychain read that fails or times out returns `null` → the caller falls
|
|
45
|
+
to needs-reauth / no-snapshot-this-cycle, retried next cycle. A `writeAsync` failure returns `false` →
|
|
46
|
+
`refreshClaudeToken` already maps a failed write to `write-skipped` (NOT needs-reauth — the exchange
|
|
47
|
+
succeeded and the still-valid credential is untouched). The `funnel.withSlotLock` per-slot lock still
|
|
48
|
+
serializes the write against a concurrent swap/refresh on the same slot regardless of sync vs async.
|
|
49
|
+
|
|
50
|
+
## 3. Blast radius
|
|
51
|
+
|
|
52
|
+
Three source files (`CredentialProvider.ts`, `QuotaPoller.ts`, `OAuthRefresher.ts`) + their three test
|
|
53
|
+
files. No credential VALUE ever leaves the funnel; no new external surface; no write-path SEMANTICS
|
|
54
|
+
change (only the off-loop execution + the new optional `writeAsync` method). The `TokenResolver` type
|
|
55
|
+
widening is backward compatible (a sync resolver is still accepted). The non-darwin `writeAsync` branch
|
|
56
|
+
uses `fs.promises` with the same `0o600` mode + recursive mkdir as the sync `write`.
|
|
57
|
+
|
|
58
|
+
## 4. Interactions
|
|
59
|
+
|
|
60
|
+
Completes the keychain leg of the dashboard event-loop-freeze work begun by the tmux Event-Loop
|
|
61
|
+
Resilience fix (v1.3.643) and the first keychain fix (PR #1248). Those took the SYNC TMUX calls and the
|
|
62
|
+
credential-AUDIT keychain read off the loop; this takes the remaining QUOTA-POLL keychain reads + the
|
|
63
|
+
refresh read-write off the loop. There is ALSO a separate, NON-keychain residual freeze (a large-JSON
|
|
64
|
+
file read) still being addressed independently — this PR does not touch it.
|
|
65
|
+
|
|
66
|
+
## 5. Rollback
|
|
67
|
+
|
|
68
|
+
Revert the three source files. The change is additive (one new optional interface method `writeAsync`,
|
|
69
|
+
one promisified `execFile` per call site, a widened union return type) plus the sync paths retained as
|
|
70
|
+
fallbacks — so a partial revert that keeps any single async conversion is also safe.
|