Skip to content

Commit 421f3b7

Browse files
authored
Merge pull request #495 from atomantic/cos/task-mpm5bdek/agent-2239802d
Warn on headless Claude Code CLI provider: June 15 2026 API-billing change
2 parents 4feeef4 + f8182e2 commit 421f3b7

4 files changed

Lines changed: 63 additions & 1 deletion

File tree

.changelog/NEXT.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ TBD
2929

3030
- **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.
3131

32+
- **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).
33+
3234
## Fixed
3335

3436
- **Universe / Series merge modal: opaque background + AI-assisted field merge.** The merge dialog (Sharing → Duplicates and the inline Universes-page resolver) had a transparent panel that bled into the underlying list, making the conflict editor hard to read. It now uses the standard `port-card` card chrome with the same `max-h-[85vh] overflow-y-auto` scroll envelope as the rest of the app's modals. Alongside that, conflicting *text* fields (e.g. two divergent `starterPrompt`s on a duplicate "Clandestiny") gain a **Merge with AI** button: the configured AI provider receives both values plus the survivor's style context (logline / style notes / embrace influences) and returns one unified value per field, which the modal renders in an editable textarea and ships as `fieldOverrides`. Each row keeps the original Keep-survivor / Use-folded picker — switching back discards the AI value for that row. Non-text conflicts (numeric, object-shaped) skip AI and stay on the manual picker. New routes: `POST /api/universe-builder/merge/ai-resolve` and `POST /api/pipeline/series/merge/ai-resolve`.

client/src/pages/AIProviders.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
22
import toast from '../components/ui/Toast';
33
import * as api from '../services/api';
44
import socket from '../services/socket';
5-
import { filterSelectableModels, providerTypeClass, isTuiProvider, isApiProvider, isProcessProvider } from '../utils/providers';
5+
import { filterSelectableModels, providerTypeClass, isTuiProvider, isApiProvider, isProcessProvider, isClaudeCodePlanCli } from '../utils/providers';
66
import {
77
formatDurationMs,
88
parseTimeoutMs,
@@ -467,6 +467,16 @@ export default function AIProviders() {
467467
)}
468468
</div>
469469

470+
{isClaudeCodePlanCli(provider) && (
471+
<div className="mt-2 text-xs rounded-md border border-port-warning/40 bg-port-warning/10 text-port-warning px-2.5 py-2 leading-relaxed">
472+
⚠️ Starting <span className="font-semibold">June 15, 2026</span>, Anthropic clocks
473+
this headless Claude Code usage under <span className="font-semibold">API billing</span>
474+
it will consume extra API credits instead of your Claude Code plan. Avoid this
475+
provider; use the interactive <span className="font-semibold">Claude Code TUI</span> provider,
476+
which stays on the plan.
477+
</div>
478+
)}
479+
470480
{testResults[provider.id] && !testResults[provider.id].testing && (
471481
<div className={`mt-2 text-sm ${testResults[provider.id].success ? 'text-port-success' : 'text-port-error'}`}>
472482
{testResults[provider.id].success

client/src/utils/providers.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,29 @@ export const enabledApiProviderFilter = (provider) => Boolean(provider?.enabled)
5959
*/
6060
export const isProcessProvider = (provider) => isCliProvider(provider) || isTuiProvider(provider);
6161

62+
/**
63+
* Check if a provider is the headless Claude Code CLI (`claude --print`) whose
64+
* *provider-level config* points it at a Claude Code subscription plan — i.e. the
65+
* provider's own `envVars` do NOT route it through Bedrock or Vertex (those bill
66+
* via the cloud account, not the plan). Used to surface the billing-change warning:
67+
* starting 2026-06-15 Anthropic clocks this non-interactive usage under API billing
68+
* (consuming API credits) instead of the Claude Code plan, so it should be avoided
69+
* in favor of the interactive Claude Code TUI provider.
70+
*
71+
* Contract is intentionally provider-level only: this is a client-side heuristic
72+
* that sees just the saved provider record. A user who routes the spawn to
73+
* Bedrock/Vertex *globally* via `~/.claude/settings.json` (merged below
74+
* `provider.envVars` in `server/services/agentCliSpawning.js`) rather than on the
75+
* provider would be cloud-billed but still match here. Configure Bedrock/Vertex on
76+
* the provider's `envVars` (as the shipped `claude-code-bedrock` sample does) to
77+
* suppress the warning.
78+
*/
79+
export const isClaudeCodePlanCli = (provider) =>
80+
isCliProvider(provider) &&
81+
provider?.command === 'claude' &&
82+
!provider?.envVars?.CLAUDE_CODE_USE_BEDROCK &&
83+
!provider?.envVars?.CLAUDE_CODE_USE_VERTEX;
84+
6285
/**
6386
* Resolve the provider whose timeout is the "fallback" for a stage — the
6487
* stage's pinned provider when set, otherwise the active provider. Used to

client/src/utils/providers.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
isCliProvider,
88
isApiProvider,
99
isProcessProvider,
10+
isClaudeCodePlanCli,
1011
enabledApiProviderFilter,
1112
providerTypeClass,
1213
getProviderTimeout,
@@ -85,6 +86,32 @@ describe('provider type predicates', () => {
8586
});
8687
});
8788

89+
describe('isClaudeCodePlanCli', () => {
90+
it('matches a headless claude CLI provider on the plan', () => {
91+
expect(isClaudeCodePlanCli({ type: 'cli', command: 'claude', envVars: {} })).toBe(true);
92+
expect(isClaudeCodePlanCli({ type: 'cli', command: 'claude' })).toBe(true);
93+
});
94+
95+
it('does not match the interactive Claude Code TUI provider', () => {
96+
expect(isClaudeCodePlanCli({ type: 'tui', command: 'claude' })).toBe(false);
97+
});
98+
99+
it('does not match non-claude CLI providers', () => {
100+
expect(isClaudeCodePlanCli({ type: 'cli', command: 'gemini' })).toBe(false);
101+
expect(isClaudeCodePlanCli({ type: 'cli', command: 'codex' })).toBe(false);
102+
});
103+
104+
it('excludes Bedrock/Vertex-routed claude CLIs (billed via cloud, not the plan)', () => {
105+
expect(isClaudeCodePlanCli({ type: 'cli', command: 'claude', envVars: { CLAUDE_CODE_USE_BEDROCK: '1' } })).toBe(false);
106+
expect(isClaudeCodePlanCli({ type: 'cli', command: 'claude', envVars: { CLAUDE_CODE_USE_VERTEX: '1' } })).toBe(false);
107+
});
108+
109+
it('safely returns false for nullish input', () => {
110+
expect(isClaudeCodePlanCli(null)).toBe(false);
111+
expect(isClaudeCodePlanCli(undefined)).toBe(false);
112+
});
113+
});
114+
88115
describe('enabledApiProviderFilter', () => {
89116
it('keeps only enabled api providers', () => {
90117
const list = [

0 commit comments

Comments
 (0)