Ghostty Blackhole: a ray-traced black hole that tracks your Claude Code context window
Ghostty Blackhole puts a real, ray-traced black hole inside your terminal. It grows as Claude Code's context window fills up, live. A fresh session is a quiet hole in the corner. A full one swallows half your screen. You'll always see /compact coming.
At a glance
- What is it?
- Ghostty Blackhole is a single GLSL fragment shader for the Ghostty terminal that beam-traces Schwarzschild geodesics live and grows with Claude Code's context fill. It is a visual gauge, not a monitor, and its pomodoro mode is hobbled by an unpopulated iDate uniform.
- Who is it for?
- Adopt Ghostty Blackhole if you run Claude Code inside Ghostty and want context pressure visible without watching a status line, and if you accept that the shader does no measurement of its own. Skip it if you want a real per-streak pomodoro timer, since the README states stock Ghostty through 1.3 never populates iDate and the wall-clock cycle therefore does not advance.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 99 days ago.
- What is it written in?
- Mainly GLSL, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What Ghostty Blackhole actually measures, and what it only depicts
The project places a black hole inside a Ghostty terminal window. It is not a status indicator with a decorative skin. The hole's apparent size is the reading, and the README frames the point bluntly: a fresh session is a small hole in the corner, a full context window swallows half the screen, and you always see /compact coming.
The audience is narrow and specific. You need Ghostty as your terminal, and for the default mode you need Claude Code running in it. If you use neither, the shader still compiles and still renders, but what remains is a drifting accretion disk with no signal attached. That is a legitimate use, and MODE_DEMO exists for exactly that, but it is not the reason the project exists.
The important distinction is between rendering and measurement. The shader computes the physics of a Schwarzschild black hole from scratch on every frame. It does not compute your token usage. That number arrives from outside, and the mechanism for getting it in is the most interesting engineering decision in the repository.
Live geodesic integration instead of precomputed lookup tables
Eric Bruneton's black hole shader, which the README names as the model, beam-traces Schwarzschild geodesics against precomputed lookup tables. Ghostty custom shaders are a single Shadertoy-style fragment pass with no custom textures, so the tables are unavailable. The project replaces them with live integration: every pixel near the hole integrates its own null geodesic through the Schwarzschild metric, using the Binet-form acceleration a = -3/2 h² x/r⁵.
That is the whole architectural story. There is no CPU-side renderer, no offscreen buffer, no accumulation across frames. A shader is stateless, and Ghostty exposes no custom uniforms, so nothing persists between frames. Everything the image needs must be derivable from the built-in inputs on each pass.
What falls out of that integration is not painted on. Rays with impact parameter under b_crit = (3√3/2) r_s spiral through the horizon and return black, which is the shadow. Escaped rays are projected back onto the terminal sky plane, so your text bends, magnifies, and shows a mirrored secondary image inside the Einstein ring. Far from the hole the shader hands off to the analytic weak-field deflection α = 2r_s/b, so only pixels near the hole pay for the integration. Blue bends slightly more than red out there, which the README describes as a touch of chromatic aberration.
The accretion disk is a thin Keplerian disk a ray can pierce several times, so the far side arcs over and under the shadow. Its color comes from a Shakura-Sunyaev temperature profile rendered as blackbody color, shifted by the relativistic factor g = √(1 − 1.5 r_s/r)/(1 − β·k̂). The approaching side is blue-hot and boosted by g^N; the receding side is dim and red. The photon ring at the 1.5 r_s photon sphere is emergent rather than drawn, and the disk pattern at radius r advances at the proper rate √(1 − 1.5 r_s/r), so inner orbits visibly freeze. A lensed starfield is available but off by default, enabled by raising STAR_GAIN.
The cost of all this is that the physics runs in a fragment shader on every frame, and the README does not publish any frame-time or GPU-utilisation figures. Whether that matters depends on your machine and your terminal size, and the repository gives you no number to reason with.
How the token count reaches a shader that takes no uniforms
Ghostty custom shaders take no custom uniforms, but they do receive the cursor color through iCurrentCursorColor, and any program can set the cursor color with a standard OSC 12 escape sequence. The project uses that as a side channel. A bundled script, claude-token.py, is wired into Claude Code and encodes the context fill into the cursor color, which the shader then reads back out.
This is a genuinely clever workaround for a real constraint, and it is also the project's main fragility. The channel is shared with anything else that sets the cursor color. A shell prompt theme, a tmux configuration, or an editor that recolours the cursor will fight the script, and the README does not document a conflict resolution. If your setup already uses OSC 12, you should expect to choose between that use and this one.
In token mode the geometry follows the number. An empty context gives a small hole in the top-right corner sized to TOKEN_AREA_MIN, default 0.06 percent of terminal area, which keeps the felt size constant across window shapes. At full context it grows toward TOKEN_AREA_MAX, default about 3 percent of terminal area for the shadow, with the bright disk reaching roughly three times further. It also moves faster and its allowed roam box expands left and down until it covers the playable screen above the work area. A /compact or a new session snaps it back to the corner seed, and with no Claude session running the hole disappears entirely and you get a plain terminal.
The hole drifts on a slow Lissajous path confined to the upper part of the screen. The bottom WORK_AREA fraction, where your prompt lives, is never distorted. Drift speed and reach follow the hole's size, so it is small and calm at the start and big and restless when context is nearly full.
Installing the shader and seeing the hole for the first time
The repository ships blackhole.glsl at the top level, alongside claude-token.py, a tuner directory, and the demo assets. The README's landing page is at s13k.dev/blackhole. Configuration is done by editing constants near the top of blackhole.glsl, and the size mode is selected there too.
The first thing to set is the size mode. MODE_TOKENS is the default and requires the bundled command. MODE_POMODORO needs no setup beyond the shader. MODE_DEMO runs a self-contained 42-second loop for recording.
// near the top of blackhole.glsl
#define SIZE_MODE MODE_TOKENSThe token channel is the cursor color, so the script that writes it has to be wired into Claude Code. The README describes claude-token.py as the bundled script for this purpose; it does not spell out the exact hook invocation, so read the script's own header before wiring it in rather than guessing at an argument.
python3 claude-token.pyFor recording or for a first look without Claude Code running, the demo loop is toggled by a shell script that also reloads Ghostty.
./demo-mode.sh on
./demo-mode.sh offWhat you should see in demo mode is the hole growing from the corner seed to full size over the loop, while the disk look tours the tuner presets in order: Inferno, Gargantua, M87* donut, Face-on ember, Quasar, Blazar, Pure lens, and back to Inferno, crossfading at each roughly five-second slot boundary. Everything runs off iTime inside one compiled shader, so a recording never hitches on a reload. The cursor channel is ignored in demo mode, which means a live Claude session cannot disturb a recording. The loop restart is obvious because the hole snaps back to the corner seed.
The pomodoro mode is an hourly bell, not a stopwatch
Pomodoro mode computes its break reminder entirely inside the shader. No daemon, no shell hooks, nothing outside blackhole.glsl. That self-containment is the design goal, and it produces a limitation the README states plainly rather than hiding.
Shaders are stateless, so a shader cannot remember when your work streak began. Instead the schedule is anchored to the wall clock through iDate. The hole is always present while you work, starting small at the cycle floor and growing over WORK_PERIOD_MIN, default 55 minutes, then collapsing back to small in the last minute and staying small through BREAK_MIN, default 5 minutes. With those defaults the peak lands at five-to-the-hour, a fixed and predictable rhythm. A typing detector tracks cursor activity through iTimeCursorChange; stop using the terminal for IDLE_FADE_SEC, default 90 seconds, and the hole shrinks live, gone entirely after a few minutes of quiet.
The trade-off is explicit in the README: the cycle will not re-anchor to a break you take at an odd time. It is an hourly bell, not a per-streak stopwatch. If you want a timer that follows your actual work rhythm, this is the wrong tool, and no amount of tuning constants fixes it, because the missing capability is state and the shader has none.
The larger problem is the caveat attached to iDate. Stock Ghostty through version 1.3 declares iDate but never populates it, so it is always zero. On current releases the wall-clock schedule does not advance at all: the hole sits at its small cycle-floor size and only the typing detector works. The README says the full cycle comes alive once Ghostty wires iDate up, and that TIME_SCALE can preview it today because that path runs off iTime instead. Token mode, the default, is unaffected by this. Anyone choosing pomodoro mode on a current Ghostty release should understand that they are choosing the typing detector plus a static hole.
When a black hole in your terminal is the wrong idea
The obvious alternative is not another shader. It is a status line, a tmux segment, or a shell prompt that prints the context figure as text. Those approaches show you the exact number and cost nothing in GPU time, and they work in any terminal. Ghostty Blackhole trades precision and portability for ambient awareness: you do not read it, you notice it. If you need the actual token count, a status line is strictly better, because the shader never computes one.
Within the project's own space, the comparison is with Bruneton's original shader. That one beam-traces against precomputed lookup tables, which is cheaper per pixel and was designed for a general rendering context. Ghostty Blackhole gives up the tables because a Ghostty custom shader is a single fragment pass with no custom textures, and pays for it with live integration on every pixel near the hole. The README's own framing is that only pixels near the hole pay for the integration, with an analytic weak-field handoff further out, so the cost is bounded but not zero and not quantified.
A third option is to do nothing. A terminal that renders text without a gravitational lens is a perfectly good terminal, and the shader does distort the text underneath it. The README confines the hole to the upper part of the screen and keeps the bottom WORK_AREA fraction undistorted, which is a deliberate concession that the effect is intrusive where it lands.
Licence, maintenance and the cost of upgrading
The project is MIT licensed, which permits commercial and closed-source use with the usual requirement to carry the licence text. That is a permissive choice and it matters here because the shader is meant to be edited: SIZE_MODE, TOKEN_AREA_MIN, TOKEN_AREA_MAX, WORK_AREA, WORK_PERIOD_MIN, BREAK_MIN, IDLE_FADE_SEC, STAR_GAIN, DILATION_MIN and TIME_SCALE are all constants you are expected to change. None of this is legal advice; read the LICENSE file in the repository for the actual terms.
Maintenance is a real consideration. The last push to the default branch was on 2026-06-11, and the repository is not archived. That is more than six months before today, so it is not accurate to describe the project as under active development. No releases were retrieved, so there is no versioned artifact to upgrade against; you track the main branch or you pin a commit yourself.
The upgrade cost is concentrated in one place. Because the shader depends on Ghostty's built-in uniform set, and because the pomodoro mode depends specifically on iDate being populated, a Ghostty release that changes or extends those uniforms can alter behaviour without any change to this repository. The README already documents one such gap between what Ghostty declares and what it populates. The same class of mismatch could appear again, and there is no compatibility matrix in the repository to check against.
Editorial conclusion
Adopt Ghostty Blackhole if you run Claude Code inside Ghostty and want context pressure visible without watching a status line, and if you accept that the shader does no measurement of its own. Skip it if you want a real per-streak pomodoro timer, since the README states stock Ghostty through 1.3 never populates iDate and the wall-clock cycle therefore does not advance. Verify first that your Ghostty build accepts a custom shader path, that the OSC 12 cursor-color channel is free in your setup, and that claude-token.py can write the token count without colliding with another program that also sets the cursor color.
Frequently asked questions
What is Ghostty Blackhole used for?
It renders a ray-traced black hole inside a Ghostty terminal window, with its size driven either by a built-in pomodoro clock or by how full Claude Code's context window is. The default mode, MODE_TOKENS, makes the hole grow as the context fills and snap back to the corner after /compact or a new session.
Does Ghostty Blackhole measure my Claude Code token usage itself?
No. Ghostty custom shaders take no custom uniforms, so the token count is carried in through the cursor color, which any program can set with a standard OSC 12 escape. The bundled script claude-token.py writes that value, and the shader only reads it back.
Why does the pomodoro cycle not advance in Ghostty Blackhole?
The README states that stock Ghostty through version 1.3 declares iDate but never populates it, so it is always zero and the wall-clock schedule does not move. On those releases the hole sits at its small cycle-floor size and only the typing detector works, though TIME_SCALE can preview the cycle because it runs off iTime.
Does Ghostty Blackhole distort the text where I type?
The hole drifts on a Lissajous path confined to the upper part of the screen, and the bottom WORK_AREA fraction, where your prompt sits, is never distorted. Text in the upper region is lensed and text behind the hole is gone.
Community notes