AI Coding Agents

Fable Broke T3 Code and Could Not Diagnose It: The GPU Performance Postmortem

Direct Answer

The models did not solve T3 Code's idle-performance bug because they began with plausible code stories instead of isolating the system that was actually busy. GPT-5.6 Sol first blamed React, WebSocket traffic, history scans, persistence, and syntax highlighting. That produced a very large CPU-focused change without materially reducing Theo's GPU-process load. Fable later found useful animation and visual-effect candidates, but Theo still had to identify the causal combination and correct the visual regressions.

The final patch is much more interesting than the headline. Infinite pulse and ping indicators kept the browser compositor producing frames at every vsync. On Theo's 120 Hz display, that meant an otherwise idle page never became visually idle. A fixed, full-viewport SVG grain overlay then forced each of those frames through an expensive blend over the whole window. In the merged patch's recorded test, changing both sides of that interaction reduced idle GPU utilization from more than 30% to about 4% with indicators active.

JQ AI SYSTEMS verdict: when an agent cannot explain a performance bug, stop asking it for fixes. Ask it to build a reversible experiment surface. The model can search a codebase and automate toggles faster than a human; the engineer must decide which measurement changes the diagnosis.

Watch the Postmortem

Video and incident credit: Theo - t3.gg. Watch the original video, follow @theo on X, and inspect the open-source T3 Code repository. The video contains a Blacksmith sponsorship. This article is independent and not sponsored.

The phrase "Fable broke my app" is Theo's framing. The repository record is more nuanced: an earlier version of the UI included model-assisted visual choices, Fable co-authored parts of the CPU investigation, and the final GPU patch also carries a Fable co-author credit. The useful conclusion is not that one model is uniquely bad. It is that fluent code generation and causal diagnosis are different capabilities.

The Incident in One Table

StageObservationWhat it suggestedWhat happened next
Initial symptomT3 Code felt responsive, but the browser GPU process consumed roughly 13-20% at lower resolution and reportedly approached 50% on Theo's 5K 120 Hz setup.The bottleneck was not ordinary input latency or a slow network request.Theo reproduced the load in a clean Chrome window with only T3 Code open.
First agent diagnosisSol focused on WebSockets, history scans, React updates, persistence, and highlighting.A familiar CPU and main-thread performance story.A change exceeding 10,000 lines did not materially improve the GPU symptom.
Process isolationOpening Google in the isolated browser reduced the GPU process sharply.The browser and machine were capable of idling; T3 Code triggered the work.Theo shifted from application logic to visual rendering and compositing.
Broad disable testA generated console helper disabled animations, transitions, filters, shadows, blur, media, composer effects, and grain.At least one item in the visual stack was causal.GPU load fell to roughly 3%; resetting the page restored the load.
Manual bisectionDisabling animations produced the largest immediate reduction.Persistent animation, not React traffic, was the dominant path.The investigation narrowed to small pulse and ping status indicators.
Interaction foundSmall infinite animations were expensive mainly when combined with a fixed full-screen grain layer and other composited effects.The bug was an interaction, not one obviously expensive component.The final patch changed animation timing and moved grain behind content.
Patch measurementThe PR reports more than 30% idle GPU before and about 4% after on a 120 Hz display.The hypothesis survived a before-and-after test.Visual seams introduced during the optimization required two follow-up fixes.

Those percentages are creator and pull-request measurements, not portable benchmarks. Browser, operating system, resolution, refresh rate, open windows, HDR behavior, DevTools state, and active status indicators all change the result. Theo's own final scare illustrates the problem: after shipping the fix, GPU usage still looked high because two separate Claude pages were open. Closing those pages produced the expected T3 Code result.

Why the Agents Guessed Wrong

Sol's first answer sounded like a senior performance review because the suspected paths were real paths: event streams can fan out, reducers can repeatedly sort history, persistence can serialize too often, and syntax highlighting can become expensive. The related CPU performance pull request documents legitimate work on bounded replay, incremental projections, cached timelines, debounced persistence, and sidebar prewarming.

The problem was not that every observation was false. The problem was that the proposed mechanism did not predict the measured symptom. An idle-looking page with a busy browser GPU process, tiny scripting and layout activity, and a large increase on a high-refresh high-DPI display should reduce confidence in a server-and-React diagnosis. The agent optimized code it could explain instead of first proving that code controlled the metric Theo cared about.

Failure modeHow it appeared hereBetter instruction
Prior-driven diagnosisThe model selected common React and streaming hot paths.List hypotheses by subsystem, then name the observation that would falsify each one.
Summary inheritanceA Chrome-generated AI summary became evidence even though it did not fit the process-level behavior.Treat profiler summaries as leads; inspect raw traces and run controlled toggles.
Large speculative patchThousands of lines changed before a causal variable was isolated.Require a minimal experiment that changes the target metric before product code is edited.
Single-component thinkingThe agents searched for one expensive animation or component.Test interactions among animation, layer promotion, filters, overlays, resolution, and refresh rate.
Tool mismatchMain-thread tools showed little activity while compositor work continued elsewhere.Combine process monitoring, browser traces, rendering tools, isolated tabs, and repeatable page states.
Premature success languageThe agent declared a performance fix before Theo's target metric improved.Define acceptance as a measured threshold on representative hardware, not a clean diff or passing tests.

The Useful Pivot: Make the Agent Build the Experiment Harness

At about 11:42, Theo changes the job. The agent is no longer asked to know the answer. It is asked to create a production-page console helper, exposed as window._t3gpu, that can turn suspect visual systems on and off without rebuilding the application after every theory.

The generated harness grouped candidates such as:

  • CSS animations and transitions;
  • filters, blur, shadows, and backdrop effects;
  • the chat composer and full-screen grain layer;
  • media and other elements likely to receive their own compositing layer;
  • individual pulse, ping, status, and typing indicators.

Applying every override damaged the interface, but that was acceptable for diagnosis. The GPU process dropped to roughly 3%. Resetting the helper restored the high load. That one reversible A/B result contributed more than the earlier large patch because it established that the causal path lived somewhere in the visual stack.

Theo then separated animation from transition rules and tested categories individually. Noise alone helped a little. Blur alone did not explain the load. Disabling animations produced the large drop. A second generated tool enumerated and paused active animations, letting him test small status elements instead of reading the entire CSS and component tree manually.

The pattern: use agents to increase experimental throughput. Let them find every animation, generate toggles, automate state setup, and record results. Do not let their first narrative decide which experiment matters.

The Real Root Cause Was an Interaction

The visible offender looked absurdly small: a pulsing terminal icon in the sidebar, plus related typing and thread-status indicators. Tailwind's ordinary animate-pulse and animate-ping utilities are not inherently broken. In this page, however, several infinite animations kept changing opacity or scale indefinitely. On a 120 Hz display, the compositor had a reason to produce another frame up to 120 times each second even when the application data was quiet.

That still does not explain the full cost. The page also had a low-opacity SVG feTurbulence grain texture attached as a fixed body::after overlay across the viewport. Each animation frame had to be composited with that full-window layer. High resolution increased the pixel area; high refresh rate increased how often the work repeated. Backdrop blur and other surfaces made the layer tree more sensitive, but the final patch identifies the always-changing indicators plus fixed grain as the decisive combination.

small infinite status animations
        x
120 Hz refresh rate
        x
high-DPI / 5K pixel area
        x
fixed full-viewport grain overlay
        =
expensive frames while the app appears idle

This is why model confidence was misleading. None of the ingredients looks catastrophic in isolation. The expensive behavior emerges from the browser's rendering architecture and the exact display environment. Source-code search can find the pieces; only measurement can establish their interaction.

What the Final Patch Actually Changed

The three-commit GPU performance patch did not simply remove animation. It preserved status motion while reducing how often the animated value changed, and it removed the grain texture from the per-frame blend path.

BeforeAfterReason
Infinite Tailwind animate-pulse at terminal, project, thread, and typing indicators.Custom animate-status-pulse with long flat opacity holds and short stepped ramps.The value changes during roughly 20% of the cycle instead of continuously.
Infinite animate-ping at connection, browser cursor, recording, and local-server indicators.A stepped burst followed by a long invisible hold.The UI keeps immediate feedback without continuous interpolation.
Fixed body::after grain overlay above the entire app.Grain baked into the body background and selected chrome surfaces.The texture can be cached with each surface instead of re-blended over every animated frame.
Removing the overlay darkened some surfaces and exposed seams.A reusable surface-grain utility restores grain to the sidebar and inset surfaces.Performance fixes still need visual parity, especially across SDR and HDR displays.
The lower composer strip mixed transparency from a lighter card color.The strip mixes from the page background while retaining backdrop blur.It eliminates the lighter full-width seam without discarding the frosted effect.

The pull request reports a reduction from more than 30% to about 4% idle GPU utilization on a 120 Hz display with indicators active. The video reports higher peaks on a 5K setup and lower numbers at 720p. Both can be true because they describe different measurement conditions. The honest unit is not "T3 Code uses X percent." It is a reproducible before-and-after result on a declared device, browser, viewport, refresh rate, and page state.

Compositor-Only Is Fast, Not Free

Standard web-performance guidance recommends animating transform and opacity because the browser can often handle them during composition without repeating layout and paint. That guidance remains correct. The mistake is upgrading "usually cheaper" into "costless in every layer tree."

MDN's animation guidance notes that code-driven animations can still consume CPU and that layer-promoted transforms and opacity changes move work to composition. Chrome's runtime-performance documentation similarly treats animation and idle behavior as measurable phases, and recommends recording traces, checking FPS and CPU, and reducing the work visible in each frame. T3 Code adds an important field example: a composited animation can be smooth and still waste energy because it prevents the page from settling.

  • High refresh rate multiplies opportunity: a 120 Hz screen can request twice as many frames as a 60 Hz screen.
  • High pixel density multiplies area: a large 5K surface contains far more pixels to blend than a 720p test window.
  • Layer count changes cost: many tiny promoted indicators can keep several surfaces active.
  • Overlays change the equation: a fixed translucent layer may require composition over the entire viewport.
  • DevTools changes the environment: debugging instrumentation can affect timing, so verify with the tools closed and an external process view where possible.
  • Accessibility remains part of performance: respect prefers-reduced-motion and provide a static path for users who request it.

A Better AI-Assisted Workflow for Weird Performance Bugs

  1. Write the symptom as a measurable contract. Record browser, OS, device, viewport, resolution, refresh rate, power state, page state, open windows, and the metric that must improve. "Laptop gets hot" is a clue; "isolated browser GPU process remains above 25% for 60 seconds on an idle 5K 120 Hz page" is testable.
  2. Isolate the owner of the work. Use a clean browser profile or separate browser with one tab. Compare another simple page. Close unrelated windows. Separate local harness processes from remote machines. Confirm whether CPU, GPU, network, memory, or disk changes with the target page.
  3. Collect more than one view. Chrome's Performance Monitor tracks CPU, heap, DOM nodes, listeners, layouts, and style recalculations. The Performance panel exposes frame and main-thread traces. Browser or OS task managers can reveal process-level behavior with DevTools closed. No single panel explains every subsystem.
  4. Ask the agent for an inventory, not a verdict. Search for infinite animations, requestAnimationFrame, timers, videos, canvases, fixed overlays, filters, backdrop blur, large shadows, observers, background polling, WebSockets, and code that scales with history. Require file and selector references.
  5. Generate reversible switches. Build a runtime helper or feature flags that disable one subsystem at a time. Include applyAll() and reset(). The first goal is a large repeatable metric change, not a pretty interface.
  6. Bisect interactions. After a category moves the metric, split it again. Test animation versus transition, one status family versus another, overlay present versus absent, 60 versus 120 Hz, low versus high resolution, and active versus idle thread state.
  7. Make the smallest product change that preserves intent. Duty-cycle or stop invisible motion, move overlays out of hot paths, reduce affected area, cache surfaces, and respect reduced-motion preferences. Avoid deleting useful feedback merely because the first model suggested it.
  8. Verify performance and appearance separately. Capture before and after metrics with a fixed protocol. Then inspect light and dark themes, SDR and HDR if relevant, mobile and desktop, active and idle states, and any seam or tint exposed by moving layers.
  9. Ship with an idle budget. Add a repeatable browser scenario or manual release check that opens the representative state, waits for settling, and records the process. Performance regressions often return because no acceptance gate represents the expensive environment.

Copy-Ready Diagnostic Prompt

Act as a performance investigation engineer.

Do not propose or implement a broad fix yet.

Symptom:
- Target metric: [CPU, GPU process, memory, FPS, battery, network]
- Baseline value: [measured value and duration]
- Environment: [device, OS, browser, viewport, resolution, refresh rate]
- Reproduction state: [exact page and active UI state]
- Control result: [same browser on a simple page]

Your job:
1. Separate hypotheses by subsystem: server, network, JS/main thread,
   layout/paint, compositor/GPU, media, extensions, and other tabs.
2. For each hypothesis, name the observation that would falsify it.
3. Inventory all candidate code with file paths and selectors.
4. Build a reversible runtime diagnostic helper with:
   - one toggle per subsystem,
   - applyAll(), reset(), and currentState(),
   - no persistent data changes,
   - no production deployment.
5. Provide a fixed measurement protocol and results table.
6. Change one variable per run, then test likely interactions.
7. Distinguish observed evidence from inference.
8. Stop before editing product code and report the smallest causal set.

Acceptance gate:
- A candidate cause must produce a repeatable change in the target metric.
- A fix must preserve visual and functional behavior.
- Passing tests or a plausible explanation are not performance evidence.

This prompt deliberately prevents the model from rewarding itself for a large diff. The first deliverable is an instrumented question. Product code changes begin only after the evidence narrows the causal set.

Six Rules for Engineering Teams Using Coding Agents

RuleWhy it matters
No large performance patch without a baseline and acceptance metric.A big optimization can improve real code while completely missing the reported incident.
Agent explanations are hypotheses until a controlled test changes the metric.Fluent narratives are cheap; causal evidence is the scarce output.
Keep diagnostic code separate and reversible.Temporary CSS overrides, tracing hooks, and test scripts should answer questions without becoming accidental product architecture.
Measure idle behavior, not only load and interaction speed.A responsive app can still consume energy, heat a laptop, and reduce battery life while nothing appears to happen.
Test the displays and browsers that amplify the failure.60 Hz, low-DPI development can hide a compositor problem that becomes obvious at 120 Hz and 5K.
Review visual regressions with the same seriousness as the metric.The first optimization exposed gray seams and tint differences, especially on HDR hardware. A faster broken interface is not accepted work.

Video Chapters

TimeSectionWhat to watch for
00:00The performance mysteryA responsive app with a browser GPU process consuming up to creator-reported 50% on the largest setup.
02:29Blacksmith sponsorSponsored CI segment; separate from the T3 Code diagnosis.
03:35How the problem surfacedHeavy agent use, remote machines, and a laptop that remained hot after game streaming stopped.
05:30Browser isolationWhy remote harness processes could not explain the local GPU load.
08:10The confident wrong diagnosisSol optimizes WebSockets, React updates, history, persistence, and highlighting.
09:45Switching from CPU to GPURefresh rate and display resolution become diagnostic clues.
11:42Changing the agent's jobThe model builds a console-controlled experiment harness instead of another speculative fix.
13:56Animations implicatedDisabling animation collapses GPU-process usage.
15:43The tiny pulsing iconAn ordinary status animation becomes the lead suspect.
18:43Fable's animation inventoryUseful search mixed with more irrelevant suspects.
20:13Blur, grain, and compositionThe fixed noise layer and persistent motion form the expensive interaction.
21:25Visual regressionsRemoving grain and retinting surfaces exposes seams on HDR displays.
23:12The second false alarmOther idle Claude windows distort the browser-wide GPU measurement.
24:56The engineering lessonAgents search and build tools; experienced humans still steer ambiguous diagnosis.

Bottom Line

T3 Code's bug is a compact lesson in why agentic engineering still needs engineering judgment. The agents found real code smells, generated substantial optimizations, inventoried animations, and built excellent diagnostic helpers. They also repeatedly overfit to the wrong explanation, suggested removing useful motion, and failed to preserve subtle visual behavior without human correction.

The winning division of labor was not human versus model. It was human causal reasoning plus machine-scale search and instrumentation. Theo supplied the surprising observations, rejected stories that did not predict them, and chose the next test. Sol and Fable accelerated code search, toggle generation, and implementation. That is a stronger model for serious debugging than asking an agent to "find and fix the performance problem" and trusting the first enormous pull request.

Sources

Common questions

What caused T3 Code to use so much GPU while idle?
Theo's final patch identified an interaction between infinite status animations and a fixed full-viewport SVG noise overlay. The animations kept the compositor producing frames at every display refresh, while the overlay made those frames more expensive to blend. The effect was strongest on a high-resolution 120 Hz display.
Was React or the WebSocket server responsible?
Not for the GPU problem shown in the video. An earlier agent-generated performance change optimized event replay, projections, persistence, and sidebar work, but Theo reported no meaningful improvement to the idle GPU symptom. Those CPU improvements may be valid for their own workloads; they did not explain this incident.
Why did Fable and GPT-5.6 Sol fail to diagnose the bug?
They initially reasoned from familiar code patterns and incomplete profiler summaries instead of a causal experiment. Once Theo supplied process-level observations and asked the agents to build toggles for animations, transitions, filters, blur, shadows, media, and noise, they became useful for search and instrumentation even though he still had to choose and interpret the experiments.
Does an opacity animation always create a GPU problem?
No. Opacity and transform are commonly efficient because they can run in the compositing stage. The lesson is that compositor-only does not mean zero cost. Infinite animations, many promoted layers, high refresh rates, high pixel density, filters, and full-screen overlays can interact in expensive ways. Measure the actual page on representative hardware.
What is duty-cycling an animation?
Duty-cycling concentrates visible motion into a short part of a longer animation cycle and holds the property flat for the rest. T3 Code replaced always-changing pulse and ping animations with stepped ramps and long opacity holds, reducing the portion of the cycle that required new frames while preserving a visible status signal.
What is the best way to use an AI agent on an unfamiliar performance bug?
Ask it to inventory possible causes, build reversible toggles, automate measurements, preserve a baseline, and document evidence. Do not ask for a large fix until one variable has produced a repeatable change. The human should own the causal model, acceptance threshold, visual review, and decision to ship.
Share
X LinkedIn Reddit
Build Yours

Want a system
like this one?

Book a free 30-minute call. We map your situation, identify the highest-impact automation, and figure out if we are a fit.

Book Free 30-min Call