diff --git a/docs/public/operate/provider-fallback.md b/docs/public/operate/provider-fallback.md index 0372b2a..557d901 100644 --- a/docs/public/operate/provider-fallback.md +++ b/docs/public/operate/provider-fallback.md @@ -88,18 +88,14 @@ one more cost surface to monitor. 4. The cooldown auto-expires at the reset timestamp. Next run uses the primary again. -The cooldown file lives at `$CLAWDIE_VAR_DIR/provider-cooldowns.json` (default -`$HOME/.clawdie/state/provider-cooldowns.json`). Expired entries are dropped -on load. +The cooldown file now resolves with the same project-local precedence used by +other operator artifacts: -> **Path convention note.** The cooldown file currently uses the legacy -> `$CLAWDIE_VAR_DIR` / `$HOME/.clawdie/state/` resolution. The newer -> [test/build status files](./structured-reports/#test-build-pipeline) -> moved to repo-local `tmp/` to align with `AGENTS.md` § "Temporary File -> Storage". A future code change should harmonize provider-fallback to the -> same precedence (`AGENT_STATUS_DIR` → `CLAWDIE_VAR_DIR` → `tmp/state/`). -> Until then, if you set `AGENT_STATUS_DIR`, also set `CLAWDIE_VAR_DIR` to -> the same path so both subsystems agree. +- `AGENT_STATUS_DIR/provider-cooldowns.json` +- otherwise `CLAWDIE_VAR_DIR/provider-cooldowns.json` (legacy compatibility) +- otherwise repo-local `tmp/state/provider-cooldowns.json` + +Expired entries are dropped on load. ## Inspecting State diff --git a/src/provider-fallback.test.ts b/src/provider-fallback.test.ts index 3b99c20..f56b0dc 100644 --- a/src/provider-fallback.test.ts +++ b/src/provider-fallback.test.ts @@ -20,6 +20,59 @@ beforeEach(() => { resetProviderCooldownsForTests(); }); +describe('default persistence path', () => { + const previousAgentStatusDir = process.env.AGENT_STATUS_DIR; + const previousLegacyVarDir = process.env.CLAWDIE_VAR_DIR; + + beforeEach(() => { + delete process.env.AGENT_STATUS_DIR; + delete process.env.CLAWDIE_VAR_DIR; + }); + + afterEach(() => { + if (previousAgentStatusDir === undefined) delete process.env.AGENT_STATUS_DIR; + else process.env.AGENT_STATUS_DIR = previousAgentStatusDir; + if (previousLegacyVarDir === undefined) delete process.env.CLAWDIE_VAR_DIR; + else process.env.CLAWDIE_VAR_DIR = previousLegacyVarDir; + }); + + it('defaults to repo-local tmp/state when no env override is set', async () => { + const now = new Date('2026-04-25T18:00:00'); + markProviderBlocked('zai', new Date('2026-04-25T19:00:00'), 'cap', now); + await persistProviderCooldowns(); + const expected = path.resolve( + process.cwd(), + 'tmp', + 'state', + 'provider-cooldowns.json', + ); + const raw = await fs.readFile(expected, 'utf8'); + expect(raw).toContain('"provider": "zai"'); + await fs.rm(expected, { force: true }); + }); + + it('prefers AGENT_STATUS_DIR over the legacy CLAWDIE_VAR_DIR', async () => { + process.env.AGENT_STATUS_DIR = path.resolve(process.cwd(), 'tmp', 'status-a'); + process.env.CLAWDIE_VAR_DIR = path.resolve(process.cwd(), 'tmp', 'status-b'); + const now = new Date('2026-04-25T18:00:00'); + markProviderBlocked('zai', new Date('2026-04-25T19:00:00'), 'cap', now); + await persistProviderCooldowns(); + const preferred = path.join( + process.env.AGENT_STATUS_DIR, + 'provider-cooldowns.json', + ); + const legacy = path.join( + process.env.CLAWDIE_VAR_DIR, + 'provider-cooldowns.json', + ); + await expect(fs.readFile(preferred, 'utf8')).resolves.toContain( + '"provider": "zai"', + ); + await expect(fs.access(legacy)).rejects.toBeDefined(); + await fs.rm(preferred, { force: true }); + }); +}); + describe('parseProviderCapError', () => { it('extracts the reset timestamp from a zAI cap error', () => { const now = new Date('2026-04-25T18:00:00'); diff --git a/src/provider-fallback.ts b/src/provider-fallback.ts index bf10801..55f356e 100644 --- a/src/provider-fallback.ts +++ b/src/provider-fallback.ts @@ -43,12 +43,11 @@ const ZAI_CAP_PATTERN_NO_RESET = /\b429\s+Usage limit reached\b/iu; const DEFAULT_COOLDOWN_SECONDS = 60 * 60; // 1 hour fallback when no reset stamp function defaultPersistencePath(): string { - const explicit = process.env.CLAWDIE_VAR_DIR; + const explicit = process.env.AGENT_STATUS_DIR || process.env.CLAWDIE_VAR_DIR; if (explicit && explicit.trim()) { return path.join(explicit.trim(), 'provider-cooldowns.json'); } - const home = process.env.HOME || '/var/db/clawdie'; - return path.join(home, '.clawdie', 'state', 'provider-cooldowns.json'); + return path.resolve(process.cwd(), 'tmp', 'state', 'provider-cooldowns.json'); } export function getFallbackPolicy(): FallbackPolicy {