2026-07-17 Claude Code Teammate Panes and Cost Tables
What I set out to do
Figure out why Claude Code had started spawning subagents into tmux split panes again (the old “agent teams” behavior), and update the cost tables in claude-ops and the ccstatusline for the current model lineup.
What I actually did
- Diagnosed the tmux panes: two things combined.
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1in the nix-managed settings enables agent teams at all, and~/.claude.json(Claude’s mutable state file, not nix-managed) hadteammateMode: "tmux"persisted from earlier experimentation. Upstream changed the default fromautotoin-processin v2.1.179, but the persisted value overrode it. - Confirmed against the official agent-teams docs: the env var gates the whole feature,
teammateModeis display-only (in-process/auto/tmux/iterm2), and teams differ from subagents (own context windows, shared task list, direct teammate messaging). - Kept agent teams enabled but pinned
teammateMode = "in-process"declaratively inclaude.nixso generated settings guard against mutable-state drift; also flipped~/.claude.json. Applied viahm switch, committed as951834a. - Updated the claude-ops
token_costDuckDB view with pricing forclaude-opus-4-8(25 per MTok),claude-sonnet-5(15 sticker, ignoring the intro rate), andclaude-fable-5(50). TDD: guard tests first (red), table update (green), 464 unit tests pass. Verified against the live database: 31k previously NULL-cost opus-4-8 rows now price to $5,681.50. Committed asa76a286.
Follow-up: effective-date pricing dimension
Flat per-model rates price history at today’s list price, so the pricing CTE gained [valid_from, valid_to) TIMESTAMPTZ ranges joined on the usage timestamp (fa95476). Sonnet 5 is the first real user: intro 10 per MTok through 2026-08-31, standard 15 after, with per-tier cache rates verified against the official pricing page. July Sonnet 5 cost corrected 58. Price history across tiers: Opus cut 3x (75 → 25) at Opus 4.5, Sonnet constant at 15 since Sonnet 4 except the Sonnet 5 intro window, Haiku up 25% at 4.5, Fable 5 the new 50 ceiling. Known un-modeled: long-context premium tiers and fast-mode rates (transcripts don’t distinguish fast requests). The premium history is real: Opus 4.6 / Sonnet 4.6 launched with a >200K premium (Opus 37.50 per MTok, cache scaled off the premium base) that the 2026-03-13 1M GA removed, and Sonnet 4/4.5 still carry 22.50 above 200K. Verified none of it ever hit this dataset: all 1,580 over-200K opus-4-6 requests start 2026-03-19 (six days after GA), and Sonnet 4/4.5 usage tops out at 171K. Documented in the view comment; a size dimension gets added only if that changes.
Follow-up: agents-view border and agent teams removal
Selecting an agent in the agents view (FleetView) drew a tmux-style border with the agent name, even for top-level agents; another machine didn’t show it. Investigation ruled out agent teams entirely: the agents view and agent teams are separate features (the agent-view docs never mention teams). The border is Claude Code’s own fullscreen attach chrome. Attaching from the agents view or claude attach always uses the fullscreen renderer regardless of the tui setting (fullscreen docs, issue #73719), and tmux list-panes -a while attached proved a single real pane, so the frame is drawn in-app. It shipped without a changelog entry somewhere in 2.1.19x–2.1.21x, which makes version skew the likely explanation for the other machine. Debugging pitfall: the first list-panes check ran with no agent open, which would have missed a transient attach-time pane; re-verified while attached.
Since teams weren’t in use, removed CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS from claude.nix (2ae603c), keeping the teammateMode = "in-process" pin as a guard against the stale tmux state in ~/.claude.json if teams ever return. Removing the flag does not remove the agents-view border.
Follow-up: changelog review (2.1.163–2.1.212) and two new settings
Reviewed the last ~40 Claude Code releases against claude.nix (on 2.1.212). Two changes landed; the rest were awareness-only (broad-permission startup warning in 2.1.210 for our bare Write/Edit/Glob allows, the default→manual mode rename in 2.1.200, and the 200-default session caps in 2.1.212).
attribution.sessionUrl = false— 2.1.207 added a third attribution channel appending a claude.ai session link to commits/PRs, which our existingcommit=""/pr=""suppression didn’t cover. Confirmed it’s absent from schemastore (whoseattributionblock isadditionalProperties:false), then pulled the authoritative shape from the native binary:sessionUrl:v.boolean()with runtime guardattribution?.sessionUrl === false → return null(skip link). So it’s a boolean, not an empty string like commit/pr — the analogy would have been wrong. CLI is authoritative until the schema catches up.fallbackModel = ["claude-sonnet-5"]— 2.1.166 setting, array capped at 3, tried in order when the primary model is overloaded/unavailable/returns a non-retryable server error. Lets our Opus-pinned config degrade to Sonnet 5 instead of hard-stalling. Caveat baked into the comment: this key does not merge across settings files — highest-precedence definition supplies the whole chain.
Applied via hm switch, just check clean, committed as db23c77.
What was striking
- ccstatusline has no pricing table at all: its session-cost widget just renders
cost.total_cost_usdfrom the JSON Claude Code pipes to the statusline command. The only table that needed updating was claude-ops. ~/.claude.jsonruntime state can silently override a changed upstream default, which reads as “old behavior came back” long after the setting was touched. Pinning contested settings in the nix-generated settings.json is the durable fix.
Related
2026-07-09 Claude Settings Hardening and checkLinkTargets Root Cause
Session: JanitorAI carousel active-height feature (openrouter-interceptor)
Separate session, same day. Built a feature for the openrouter-interceptor Firefox extension: the last-bot-message carousel (JanitorAI’s “bot-choices slider”) now resizes to the height of the active variant instead of the tallest, and keeps the active card scrolled into view on swipe. Ran it through the full superpowers workflow (brainstorming → spec → plan → subagent-driven development with per-task reviews), branch feat/slider-active-height.
What made it a marathon
Six root-caused mechanism corrections, each proven against a real browser, not guessed:
- Not scroll-driven. The carousel moves by an inline
transform: translateX(-N%), never scrolls. Clipping had to be vertical-only viaoverflow: visible clip(the one legal Firefox pairing), notoverflow-y: hidden(which forcesoverflow-xand clips every sibling). - Active-slide selection went through four geometry-inference revisions (offset frames, chain walks, mixed-snapshot fixes) and an IntersectionObserver, all of which failed live. The IO version stalled outright when the carousel was scrolled off-screen (every visibility ratio 0 → no update). The actual answer was there the whole time:
translateX(-1100%)literally means card 11.computeActiveIndexFromTransform(a one-regex function already in the repo) is the truth, read synchronously. Every earlier “+1 offset” that justified the geometry work was our ownscrollIntoViewcorruptingclip.scrollLeft. scrollIntoViewwas the villain twice over. It scrolls every scrollable ancestor on both axes, including JAI’soverflow-hiddenclip window — that produced the “advances two messages per press” and clipped cards. Replaced with vertical-only manual scroll of the nearest scroller, absolutescrollTotargets.- Sticky anchor was under the chrome. JAI’s header and composer overlay the scroller, so anchoring to the scroller’s box hid the card top under the header / bottom under the composer. Fixed with an overlay-aware
visibleWindowthat probes paint order viaelementsFromPoint. - Tall cards landed mid-view because JAI’s react-virtuoso list “follows output”: when the last message grows near the bottom it yanks the scroll to the new bottom, overwriting our anchor. Resolved by re-asserting the anchor after the swipe settles, to take the final word. (Subtlety the final review caught: I first keyed this on the height
transitionend, but JAI’s inlinetransition: transformoverrides our stylesheet height transition, so height writes are instant and that event never fires — only the fallback timer was working. Corrected to key on the transformtransitionend, which fires ~0.5s in, after virtuoso’s ~150ms yank.) - Considered overriding virtuoso and rejected it. Its follow-output is indistinguishable from streaming-token growth (both are “last message got taller”), and suppressing it means monkeypatching host globals with app-wide blast radius. Two components wanting opposite scroll positions on one element can only be resolved by running last, which is what the re-assert does.
What was striking
- The truth was published; we kept re-deriving it. Reading JAI’s own transform index replaced ~250 lines of geometry inference. The recurring failure mode all session was distrusting the source of truth and reconstructing it worse.
- happy-dom tests can’t catch layout bugs — every offset was hand-stubbed to match our own (wrong) model, so 39 green tests coexisted with a broken page. The fix was a real-browser Playwright “lab” generated from an actual diagnostic capture (redacted, real virtuoso/overlay chrome modeled), which reproduced each symptom as a genuine RED before any fix. That lab is now the regression net.
- Instrument, then conclude. The virtuoso yank was only pinned after a live capture’s telemetry showed our scroll landing on target (13814) and virtuoso pulling it to the bottom (14734) within 150ms — not from reasoning.
Outcome
Feature works live across all reported cases. Final whole-branch review in progress; 14 Playwright tests (chromium + firefox) + 426 unit + typecheck + build all green. Diagnostic capture bundle extended with a data-slider-active stamp so future captures read the active card instead of recomputing it. ADR 0005 records the transform-index decision (superseding the IntersectionObserver approach the same day).
Project-note assessment
No Praxis/Projects note created. The openrouter-interceptor extension is ongoing, no-deadline work (a stream of features over time), which per 0014-no-praxis-areas-subfolder does not belong in Projects. This individual feature was time-bound but is finishing now, so a project note would be born completed. If a broader roadmap for the extension emerges, that would warrant one.