From 377df86e5bb2868a7bca76b822a02669faa6c6fc Mon Sep 17 00:00:00 2001 From: Adam Eivy Date: Mon, 25 May 2026 21:44:02 -0700 Subject: [PATCH 1/2] Warn on headless Claude Code CLI provider: June 15 2026 API-billing change --- .changelog/NEXT.md | 2 ++ client/src/pages/AIProviders.jsx | 12 +++++++++++- client/src/utils/providers.js | 14 ++++++++++++++ client/src/utils/providers.test.js | 27 +++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.changelog/NEXT.md b/.changelog/NEXT.md index 5c2c295abd..eceb04ce92 100644 --- a/.changelog/NEXT.md +++ b/.changelog/NEXT.md @@ -27,6 +27,8 @@ TBD - **Writers Room auto-collapses the header + library when you open a work.** Tapping a library item now collapses both the page header and the library so the editor gets the full screen — most useful on mobile, where the library is an inline block stacked above the editor. The header shrinks to a slim bar that hosts the "show library" control (reachable on mobile, where there was previously no expand affordance), and the library always re-appears whenever no work is selected so you're never stranded. +- **AI Providers: billing-change warning on the headless Claude Code CLI provider.** The AI Providers page now shows an inline warning on the **Claude Code CLI** (`claude --print`) provider card: starting **June 15, 2026**, Anthropic clocks this non-interactive usage under API billing — consuming extra API credits instead of your Claude Code plan — so it should be avoided in favor of the interactive **Claude Code TUI** provider, which stays on the plan. The warning is scoped to plan-billed `claude` CLI providers and intentionally excludes Bedrock/Vertex-routed variants (those bill via the cloud account, not the plan). + ## Fixed - **Ollama model installs no longer die on a transient "EOF".** Pulling a model from the Local LLMs page could fail with a bare `EOF` status and leave the model uninstalled. Ollama streams pull progress as NDJSON, and a transient network hiccup between Ollama and the model registry/CDN surfaces mid-stream as an `{"error":"EOF"}` frame (or the response read dropping outright). PortOS gave up on the first occurrence — even though the `ollama` CLI silently retries these and the pull is resumable (partial blobs are kept). `pullModel` now retries the transient/EOF class up to 3 attempts with a linear backoff, continuing from the partial download rather than restarting; non-transient errors (bad model name, missing manifest) still fail fast. The install banner shows a "retrying after network error" notice during the backoff instead of stalling. diff --git a/client/src/pages/AIProviders.jsx b/client/src/pages/AIProviders.jsx index 71439dffb1..1623ad4f9b 100644 --- a/client/src/pages/AIProviders.jsx +++ b/client/src/pages/AIProviders.jsx @@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import toast from '../components/ui/Toast'; import * as api from '../services/api'; import socket from '../services/socket'; -import { filterSelectableModels, providerTypeClass, isTuiProvider, isApiProvider, isProcessProvider } from '../utils/providers'; +import { filterSelectableModels, providerTypeClass, isTuiProvider, isApiProvider, isProcessProvider, isClaudeCodePlanCli } from '../utils/providers'; import { formatDurationMs, parseTimeoutMs, @@ -467,6 +467,16 @@ export default function AIProviders() { )} + {isClaudeCodePlanCli(provider) && ( +
+ ⚠️ Starting June 15, 2026, Anthropic clocks + this headless Claude Code usage under API billing — + it will consume extra API credits instead of your Claude Code plan. Avoid this + provider; use the interactive Claude Code TUI provider, + which stays on the plan. +
+ )} + {testResults[provider.id] && !testResults[provider.id].testing && (
{testResults[provider.id].success diff --git a/client/src/utils/providers.js b/client/src/utils/providers.js index 80c4d35e55..3e2a19983b 100644 --- a/client/src/utils/providers.js +++ b/client/src/utils/providers.js @@ -59,6 +59,20 @@ export const enabledApiProviderFilter = (provider) => Boolean(provider?.enabled) */ export const isProcessProvider = (provider) => isCliProvider(provider) || isTuiProvider(provider); +/** + * Check if a provider is the headless Claude Code CLI (`claude --print`) running + * against a Claude Code subscription plan — i.e. NOT routed through Bedrock or + * Vertex (those bill via the cloud account, not the plan). Used to surface the + * billing-change warning: starting 2026-06-15 Anthropic clocks this non-interactive + * usage under API billing (consuming API credits) instead of the Claude Code plan, + * so it should be avoided in favor of the interactive Claude Code TUI provider. + */ +export const isClaudeCodePlanCli = (provider) => + isCliProvider(provider) && + provider?.command === 'claude' && + !provider?.envVars?.CLAUDE_CODE_USE_BEDROCK && + !provider?.envVars?.CLAUDE_CODE_USE_VERTEX; + /** * Resolve the provider whose timeout is the "fallback" for a stage — the * stage's pinned provider when set, otherwise the active provider. Used to diff --git a/client/src/utils/providers.test.js b/client/src/utils/providers.test.js index ea90397694..35f1561944 100644 --- a/client/src/utils/providers.test.js +++ b/client/src/utils/providers.test.js @@ -7,6 +7,7 @@ import { isCliProvider, isApiProvider, isProcessProvider, + isClaudeCodePlanCli, enabledApiProviderFilter, providerTypeClass, getProviderTimeout, @@ -85,6 +86,32 @@ describe('provider type predicates', () => { }); }); +describe('isClaudeCodePlanCli', () => { + it('matches a headless claude CLI provider on the plan', () => { + expect(isClaudeCodePlanCli({ type: 'cli', command: 'claude', envVars: {} })).toBe(true); + expect(isClaudeCodePlanCli({ type: 'cli', command: 'claude' })).toBe(true); + }); + + it('does not match the interactive Claude Code TUI provider', () => { + expect(isClaudeCodePlanCli({ type: 'tui', command: 'claude' })).toBe(false); + }); + + it('does not match non-claude CLI providers', () => { + expect(isClaudeCodePlanCli({ type: 'cli', command: 'gemini' })).toBe(false); + expect(isClaudeCodePlanCli({ type: 'cli', command: 'codex' })).toBe(false); + }); + + it('excludes Bedrock/Vertex-routed claude CLIs (billed via cloud, not the plan)', () => { + expect(isClaudeCodePlanCli({ type: 'cli', command: 'claude', envVars: { CLAUDE_CODE_USE_BEDROCK: '1' } })).toBe(false); + expect(isClaudeCodePlanCli({ type: 'cli', command: 'claude', envVars: { CLAUDE_CODE_USE_VERTEX: '1' } })).toBe(false); + }); + + it('safely returns false for nullish input', () => { + expect(isClaudeCodePlanCli(null)).toBe(false); + expect(isClaudeCodePlanCli(undefined)).toBe(false); + }); +}); + describe('enabledApiProviderFilter', () => { it('keeps only enabled api providers', () => { const list = [ From f8182e2d08e168b1755c57ed6b15ff2d172f2708 Mon Sep 17 00:00:00 2001 From: Adam Eivy Date: Mon, 25 May 2026 21:50:25 -0700 Subject: [PATCH 2/2] address review (copilot): document provider-level-only billing-warning contract --- client/src/utils/providers.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/client/src/utils/providers.js b/client/src/utils/providers.js index 3e2a19983b..e103ff2406 100644 --- a/client/src/utils/providers.js +++ b/client/src/utils/providers.js @@ -60,12 +60,21 @@ export const enabledApiProviderFilter = (provider) => Boolean(provider?.enabled) export const isProcessProvider = (provider) => isCliProvider(provider) || isTuiProvider(provider); /** - * Check if a provider is the headless Claude Code CLI (`claude --print`) running - * against a Claude Code subscription plan — i.e. NOT routed through Bedrock or - * Vertex (those bill via the cloud account, not the plan). Used to surface the - * billing-change warning: starting 2026-06-15 Anthropic clocks this non-interactive - * usage under API billing (consuming API credits) instead of the Claude Code plan, - * so it should be avoided in favor of the interactive Claude Code TUI provider. + * Check if a provider is the headless Claude Code CLI (`claude --print`) whose + * *provider-level config* points it at a Claude Code subscription plan — i.e. the + * provider's own `envVars` do NOT route it through Bedrock or Vertex (those bill + * via the cloud account, not the plan). Used to surface the billing-change warning: + * starting 2026-06-15 Anthropic clocks this non-interactive usage under API billing + * (consuming API credits) instead of the Claude Code plan, so it should be avoided + * in favor of the interactive Claude Code TUI provider. + * + * Contract is intentionally provider-level only: this is a client-side heuristic + * that sees just the saved provider record. A user who routes the spawn to + * Bedrock/Vertex *globally* via `~/.claude/settings.json` (merged below + * `provider.envVars` in `server/services/agentCliSpawning.js`) rather than on the + * provider would be cloud-billed but still match here. Configure Bedrock/Vertex on + * the provider's `envVars` (as the shipped `claude-code-bedrock` sample does) to + * suppress the warning. */ export const isClaudeCodePlanCli = (provider) => isCliProvider(provider) &&