@indigoai-us/hq-cli 5.345.125 → 5.345.127
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.127] — 2026-10-14
|
|
6
|
+
|
|
7
|
+
## [5.345.126] — 2026-10-14
|
|
8
|
+
- Claude quota holds are off. A Claude 429 still moves that lane to another account, but it no longer blocks the account for other lanes, and any stored Claude hold is ignored. Set `HQ_LANES_CLAUDE_QUOTA_HOLDS=1` to turn the old behavior back on. Codex and Grok holds are unchanged.
|
|
9
|
+
- Remote Control lanes on the default Claude account now use your normal Claude login too. 5.345.125 fixed this for regular lanes only.
|
|
10
|
+
|
|
11
|
+
- `hq lanes` resume no longer picks a stale transcript when the same session exists in more than one account home. It uses the copy from the home the lane last ran in, and otherwise refuses with `provider_transcript_ambiguous` naming the homes.
|
|
12
|
+
|
|
5
13
|
## [5.345.125] — 2026-10-14
|
|
6
14
|
- `hq lanes` no longer sets `CLAUDE_CONFIG_DIR` for a Claude account whose
|
|
7
15
|
home is the default `~/.claude`, so lane workers, `claude auth status`
|
|
@@ -27,6 +27,16 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import type { LaneProvider } from "./types.js";
|
|
29
29
|
export declare const QUOTA_STATE_REL: string;
|
|
30
|
+
/**
|
|
31
|
+
* Claude quota holds are off. Removed by owner directive on 2026-10-14: a
|
|
32
|
+
* false weekly-limit 429 held every Claude lane for days while the account
|
|
33
|
+
* still had capacity. A Claude 429 still fails that lane over to another
|
|
34
|
+
* account; it never blocks the account for later lanes, and stored Claude
|
|
35
|
+
* holds are ignored on read. `HQ_LANES_CLAUDE_QUOTA_HOLDS=1` turns the old
|
|
36
|
+
* behavior back on; the test suite sets it to keep the hold machinery covered.
|
|
37
|
+
*/
|
|
38
|
+
export declare const CLAUDE_QUOTA_HOLDS_ENV = "HQ_LANES_CLAUDE_QUOTA_HOLDS";
|
|
39
|
+
export declare function providerHasQuotaHolds(provider: LaneProvider): boolean;
|
|
30
40
|
export declare const QUOTA_SCHEMA_VERSION = "hq-lanes-quota.v1";
|
|
31
41
|
export interface ModelQuota {
|
|
32
42
|
quota_reset_at?: string;
|
package/dist/lib/lanes/quota.js
CHANGED
|
@@ -29,6 +29,20 @@ import * as fs from "node:fs";
|
|
|
29
29
|
import * as path from "node:path";
|
|
30
30
|
import { withLaneRecordFileLock } from "./store.js";
|
|
31
31
|
export const QUOTA_STATE_REL = path.join("personal", "lanes", "quota.json");
|
|
32
|
+
/**
|
|
33
|
+
* Claude quota holds are off. Removed by owner directive on 2026-10-14: a
|
|
34
|
+
* false weekly-limit 429 held every Claude lane for days while the account
|
|
35
|
+
* still had capacity. A Claude 429 still fails that lane over to another
|
|
36
|
+
* account; it never blocks the account for later lanes, and stored Claude
|
|
37
|
+
* holds are ignored on read. `HQ_LANES_CLAUDE_QUOTA_HOLDS=1` turns the old
|
|
38
|
+
* behavior back on; the test suite sets it to keep the hold machinery covered.
|
|
39
|
+
*/
|
|
40
|
+
export const CLAUDE_QUOTA_HOLDS_ENV = "HQ_LANES_CLAUDE_QUOTA_HOLDS";
|
|
41
|
+
export function providerHasQuotaHolds(provider) {
|
|
42
|
+
if (provider !== "claude")
|
|
43
|
+
return true;
|
|
44
|
+
return process.env[CLAUDE_QUOTA_HOLDS_ENV] === "1";
|
|
45
|
+
}
|
|
32
46
|
export const QUOTA_SCHEMA_VERSION = "hq-lanes-quota.v1";
|
|
33
47
|
/** A provider reset further away than this needs a second 429 before it holds. */
|
|
34
48
|
export const QUOTA_CONFIRM_HORIZON_MS = 24 * 60 * 60 * 1000;
|
|
@@ -99,6 +113,8 @@ export function loadQuotaState(hqRoot) {
|
|
|
99
113
|
const providers = {};
|
|
100
114
|
const rawProviders = isPlainObject(parsed.providers) ? parsed.providers : {};
|
|
101
115
|
for (const provider of ["claude", "codex", "grok"]) {
|
|
116
|
+
if (!providerHasQuotaHolds(provider))
|
|
117
|
+
continue;
|
|
102
118
|
const rawAccounts = rawProviders[provider];
|
|
103
119
|
if (!isPlainObject(rawAccounts))
|
|
104
120
|
continue;
|
|
@@ -480,6 +496,8 @@ function gateFarReset(current, signal, options) {
|
|
|
480
496
|
};
|
|
481
497
|
}
|
|
482
498
|
export function recordAccountQuota(hqRoot, provider, accountId, incoming, options) {
|
|
499
|
+
if (!providerHasQuotaHolds(provider))
|
|
500
|
+
return loadQuotaState(hqRoot);
|
|
483
501
|
return withLaneRecordFileLock(quotaStatePath(hqRoot), () => {
|
|
484
502
|
const state = loadQuotaState(hqRoot);
|
|
485
503
|
const accounts = state.providers[provider] ?? {};
|
|
@@ -631,6 +649,8 @@ function resetStillAhead(iso, now) {
|
|
|
631
649
|
* no expiry. A past reset does not block.
|
|
632
650
|
*/
|
|
633
651
|
export function quotaBlockFor(state, provider, accountId, model, now) {
|
|
652
|
+
if (!providerHasQuotaHolds(provider))
|
|
653
|
+
return { blocked: false };
|
|
634
654
|
const account = state.providers[provider]?.[accountId];
|
|
635
655
|
if (!account)
|
|
636
656
|
return { blocked: false };
|
|
@@ -12,14 +12,21 @@ import { fileURLToPath } from "node:url";
|
|
|
12
12
|
import { stripVTControlCharacters } from "node:util";
|
|
13
13
|
import { daemonPaths, ensurePrivateDir } from "../daemon/paths.js";
|
|
14
14
|
import { isOwnedPoolForwarder, poolHostsFile, readRemoteControlPoolsConfig } from "../daemon/remote-control-pools.js";
|
|
15
|
-
import { accountStatus } from "./accounts.js";
|
|
15
|
+
import { accountStatus, isDefaultProviderHome } from "./accounts.js";
|
|
16
16
|
import { effectiveAccountHome } from "./account-home.js";
|
|
17
17
|
import { LanesError } from "./errors.js";
|
|
18
18
|
import { defaultProbe } from "./models.js";
|
|
19
19
|
import { encodeClaudeProjectDir } from "./move-account.js";
|
|
20
20
|
/** Keep every credential and network route tied to the selected pool. */
|
|
21
21
|
export function configureRemoteControlChildEnv(env, launch) {
|
|
22
|
-
|
|
22
|
+
// The default home runs with CLAUDE_CONFIG_DIR unset so it reads the same
|
|
23
|
+
// Keychain login as the user's own sessions (see isDefaultProviderHome).
|
|
24
|
+
if (isDefaultProviderHome("claude", launch.accountHome)) {
|
|
25
|
+
delete env.CLAUDE_CONFIG_DIR;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
env.CLAUDE_CONFIG_DIR = launch.accountHome;
|
|
29
|
+
}
|
|
23
30
|
env.NODE_EXTRA_CA_CERTS = launch.caPath;
|
|
24
31
|
env.IS_SANDBOX = "1";
|
|
25
32
|
for (const key of Object.keys(env)) {
|
|
@@ -197,7 +204,7 @@ async function runInside(args) {
|
|
|
197
204
|
if (fs.existsSync(envelopePath)) {
|
|
198
205
|
// The id was passed to Claude; verify the provider really persisted it
|
|
199
206
|
// before reporting it to the lane watcher as a resumable session.
|
|
200
|
-
const transcript = path.join(process.env.CLAUDE_CONFIG_DIR || "", "projects", encodeClaudeProjectDir(process.cwd()), `${sessionId}.jsonl`);
|
|
207
|
+
const transcript = path.join(process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude"), "projects", encodeClaudeProjectDir(process.cwd()), `${sessionId}.jsonl`);
|
|
201
208
|
for (let attempt = 0; attempt < 20 && !fs.existsSync(transcript); attempt++) {
|
|
202
209
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
203
210
|
}
|
|
@@ -172,6 +172,7 @@ function relinkFromAnyProject(input, targetHome, searched) {
|
|
|
172
172
|
scanned.push(path.join(home, "sessions"));
|
|
173
173
|
throw missingTranscript(input, searched, scanned);
|
|
174
174
|
}
|
|
175
|
+
const matches = [];
|
|
175
176
|
for (const home of searched) {
|
|
176
177
|
const found = [];
|
|
177
178
|
const dirs = claudeProjectDirs(home);
|
|
@@ -183,12 +184,30 @@ function relinkFromAnyProject(input, targetHome, searched) {
|
|
|
183
184
|
if (isFile(candidate))
|
|
184
185
|
found.push(candidate);
|
|
185
186
|
}
|
|
186
|
-
if (found.length === 0)
|
|
187
|
-
continue;
|
|
188
187
|
if (found.length > 1) {
|
|
189
188
|
throw new LanesError("provider_transcript_ambiguous", `Multiple claude transcripts for session ${input.sessionId} exist in ${home}`, { provider: input.provider, session_id: input.sessionId, homes_searched: searched, matches: found });
|
|
190
189
|
}
|
|
191
|
-
|
|
190
|
+
if (found.length === 1)
|
|
191
|
+
matches.push({ home, source: found[0] });
|
|
192
|
+
}
|
|
193
|
+
if (matches.length === 0)
|
|
194
|
+
throw missingTranscript(input, searched, scanned);
|
|
195
|
+
// The last-run home holds the newest copy; any other home may hold a stale
|
|
196
|
+
// copy from an earlier account move, so more than one is ambiguous.
|
|
197
|
+
const lastRunHome = input.providerHomeAtSpawn ? path.resolve(input.providerHomeAtSpawn) : undefined;
|
|
198
|
+
const chosen = matches.find((match) => match.home === lastRunHome) ??
|
|
199
|
+
(matches.length === 1 ? matches[0] : undefined);
|
|
200
|
+
if (chosen === undefined) {
|
|
201
|
+
throw new LanesError("provider_transcript_ambiguous", `Provider transcript for session ${input.sessionId} exists in multiple homes: ${matches.map((match) => match.home).join(", ")}`, {
|
|
202
|
+
provider: input.provider,
|
|
203
|
+
session_id: input.sessionId,
|
|
204
|
+
homes_searched: searched,
|
|
205
|
+
matching_homes: matches.map((match) => match.home),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
{
|
|
209
|
+
const home = chosen.home;
|
|
210
|
+
const source = chosen.source;
|
|
192
211
|
const targetDir = path.join(targetHome, "projects", encodeClaudeProjectDir(input.cwd));
|
|
193
212
|
const target = path.join(targetDir, `${input.sessionId}.jsonl`);
|
|
194
213
|
const subagents = path.join(path.dirname(source), input.sessionId);
|
|
@@ -212,7 +231,6 @@ function relinkFromAnyProject(input, targetHome, searched) {
|
|
|
212
231
|
found_outside_cwd: true,
|
|
213
232
|
};
|
|
214
233
|
}
|
|
215
|
-
throw missingTranscript(input, searched, scanned);
|
|
216
234
|
}
|
|
217
235
|
function copyVerifiedTranscript(input, sourceHome, targetHome, searched) {
|
|
218
236
|
let copied;
|