caprock
← Back to caprock.dev

Changelog

What's new in caprock.

Upgrade with the command for how you installed it — brew upgrade caprock, scoop update caprock, or go install github.com/dspv/caprock/cmd/caprock@latest — then caprock down && caprock up so the new binary is the one running. Caprock can also tell you when a release is out: turn on release checks in the dashboard. It is off until you switch it on, sends nothing about you, and never installs anything itself.

v0.52.32026-09-03

The loop banner sold a cap that could not act, beside a cost that was…

  • The loop banner sold a cap that could not act, beside a cost that was not the loop's. Both halves of the row overstated what they knew. The spend cap pauses only sessions Caprock started (rule 7), and 122 of the 127 sessions that did real work on this machine were started by hand — so "a cap that stops this" was, nearly every time it appeared, an offer to stop something the cap cannot reach. It now says "a cap covers sessions Caprock starts" and offers "what a cap does" unless the session is one Caprock owns; unknown ownership counts as not owned, since a promise made on a guess is the thing being fixed. The button stays, because the feature is real and the sessions where it applies are exactly the ones someone would want it for.
v0.52.22026-09-03

Sessions cleared before 0

  • 52.1 still said "compacting context". 0.52.1 gave /clear and Escape their own event kinds, but only for events recorded after it shipped — the rows already in the database kept the compact's kind, and on this machine all 18 of them were the *last* event of their session, which is the phrase the session card shows. So 17 sessions went on claiming a compaction that never happened, and the /clear that started the whole investigation was still captioned "was compacting context" beside a $1,062 cost and a 95%-full context.
v0.52.12026-09-03

/clear showed one editor as two working sessions

  • Claude Code does not end a session on /clear — it keeps the process and starts a fresh session id inside it. Both rows therefore shared a live pid, and the sweep that decides a session is over asks whether its process is still running, so the old row could never close. It sat on the dashboard as a second working session, frozen at the moment it was cleared, still presenting a six-day cost and a 95%-full context as current. The replacement is now keyed off the SessionStart that follows, which is the only one of the two hooks that names the session meant to survive.
  • A cleared session was narrated as "compacting context". /clear was stored as context.compact, which is a different event: a compact summarizes the context and keeps its substance, /clear discards it. The dashboard reported a compaction that never happened, at the one moment the owner is deciding whether to wait. It has its own kind now, and the reasons that leave a session running without touching its context (Escape at the prompt) have theirs.
  • "unknown model" appeared beside the model's own name. A session that had not answered yet has no prompt to measure, so the Context stat was empty — and captioned unknown model directly beside a Cost stat reading claude-opus-5. The two reasons a context can be missing call for opposite reactions (one clears itself in seconds, the other never will), so the server now says which applies, naming the model id when the pricing table cannot size it.
  • Fable 5 could not be picked for a new session. The model list offered three ids and omitted the most capable one on the machine, though claude --help names fable alongside opus and sonnet. All four are now offered, ordered by price, each verified by a live answer from the real claudeclaude-mythos-5 is in the pricing table and stays out, because the binary rejects it.
v0.52.02026-09-03

A switch for the session memory

  • "Sometimes I need the context and sometimes I don't" — a fair objection from the first person to live in Caprock, who had been bitten by an assistant that remembered his job title and volunteered it while he was asking about something else.
  • "Answers" is now "Memory". Two halves of one thing did not know about each other: a new session is handed what the last one left, and you can search everything the agents ever said. Same corpus — one was labelled "memory" in settings, the other "Answers" in the nav, which nobody looking for the first would open. The status row names the screen too.
  • The Memory screen says how far back it goes — "since July 19", measured from the oldest passage still held rather than from the fortnight a handoff reaches. Those are two different dates, and showing the shorter one understated the corpus by a month.
v0.51.12026-09-03

The token column beside each model counted re-reading, not work

  • "claude-opus-5 · 12.61B tokens" is arithmetically true and reads as how much went through that model. On a normal workload 98.6% of it is cache read — the same context fetched again, at a tenth of the price — against 14.6M of output. The column measured how many times one conversation was re-read.
v0.51.02026-09-03

A session opening a repository you have worked in before already know…

  • A session opening a repository you have worked in before already knows where you stopped. Open claude, and the agent has been handed the last substantial thing an agent said in that repository — no command, no prompt, nothing to press. Verified against a live session, which named the previous session's work while forbidden to read files or git.
v0.50.12026-09-03

The update instructions were set like captions

  • The commands read fine; everything explaining them was 11px in the faintest grey — the weight this interface uses for a timestamp, something you are meant to slide past. It is the half of the dialog that says why updating takes three commands and why Caprock will not do it for you. The install-route tabs got the same lift; they are a choice, and they were dimmer than the platform tabs beside them.
v0.50.02026-09-03

Let a tablet in from the dashboard, without a terminal

  • Reading Caprock from a phone shipped needing caprock up --lan, which requires a terminal on the machine — and the person who wants this is holding the tablet. It is a button on the status screen now, with an off switch beside the device list. The flag still works for a machine administered over SSH. Unchanged: it is off again when Caprock restarts, and paired devices are kept.
  • Caprock uses a Tailscale address when there is one, and says which kind of address you have. A LAN address works when the other device is on the same wifi and *never* from mobile data — no setting changes that, because the packets do not arrive. The panel said nothing about it, so the honest conclusion from a tablet on mobile data was that the feature is broken.
v0.50.02026-09-03

Let a tablet in from the dashboard, without a terminal

  • Reading Caprock from a phone shipped needing caprock up --lan, which requires a terminal on the machine — and the person who wants this is usually holding the tablet, not sitting at the machine. It is a button on the status screen now: press it, and the same panel shows the address and the code. The flag still works for a machine you administer over SSH.
v0.49.32026-09-03

"Burn now" says it is measuring instead of extrapolating

  • 0.49.1 made the rate divide by the time it actually covers, which is correct arithmetic and an alarming number: a daemon twenty seconds old showed $3,215/h, because twenty seconds of a busy minute extrapolates to that. Until the window has been lived through, the tile shows a dash and says it needs ten minutes of running — a figure nobody can act on is worse than no figure.
  • A test that asserted a promise the code never made. One of the night's new tests demanded that a healthy terminal subscriber receive 300 of 400 chunks while another subscriber sat stalled. The buffer is deliberately lossy — a reader that falls behind legitimately misses some — so the test passed on a fast machine and failed in CI, which is what stopped v0.49.2 from publishing. It asserts what the code actually promises now: the writer finishes and a healthy reader keeps receiving.
v0.49.12026-09-03

"Files touched" counted whole session lifetimes

  • The range filtered which *sessions* counted, not which *files* — so a fortnight-long session that touched 300 files put all 300 into "today" the moment it said anything today. Seven days read 417 against a true 144. The ranged answer was already on disk; the wrong table was being asked.
  • The daily session count read zero on days with real spend. It counted a session on the day of its first turn *ever*, so a session begun yesterday added nothing to today. Four of eight days read 0 against 1, 2, 2 and 2 — beside cost and token figures in the same rows that were correct, which is worse than being wrong alone. After the migration, ten recent days all match a direct count over the events.
  • A replaced transcript of the same size lost everything below the old offset. Truncation was detected by "the file got shorter", and Claude Code rewrites transcripts in place — on a resume, on a compact — where the replacement usually is not shorter. Those lines were never parsed, so nothing deduplicated them either: a session simply arrived missing its first turns.
  • A half-written Gemini record was skipped permanently. The reader advanced past everything available whenever anything parsed, which is the opposite of what its own comment promised. Since the file is read while Gemini writes it, landing mid-record is the normal case.
  • Two Gemini events in the same millisecond became one — and worse, every event whose timestamp was missing or in an unexpected format shared a single key, so only the first was ever stored.
  • The burn rate divided a partial window by its full width. A daemon three minutes old has three minutes of history and was dividing by ten, so the rate read a third of the truth with nothing saying the window was still filling.
  • Six tables can no longer push the page sideways on a narrow screen. On a tablet a wide table took the whole body with it.
  • More tests where a failure would cost something: the three shapes a tool error arrives in (only one was exercised, and it decides the red badge), the point where rule 7 is enforced, all five pairing handlers, and the file that holds device tokens. Every important test was checked by reverting the behaviour it asserts.
v0.49.02026-09-03

Read Caprock from a tablet

  • caprock up --lan opens a second listener on this machine's own private address; the status screen shows what to open on the other device and a six-digit code to type. No account, no tunnel, nothing on anybody's server — the other device is talking to your machine.
  • Two figures ignored the agent filter. Choosing opencode on the Now screen narrowed most of the Today panel and left "burn now" showing every agent's money, in the same grid row as the cost it no longer matched. The unpriced warning had the same fault, and worse: it told a reader looking at one agent that tokens were missing from a total those tokens were never in.
  • The share card's breakdown was always the last thirty days. Choosing a period changed the heading and the lit tile and nothing else that mattered, so a card headed "My week on Caprock" carried a month's models beneath it — five of them against the one that week ran, and $5,459 of Opus against $1,576. The four tiles still show every period on purpose; it is the breakdown that belongs to the period the card names.
  • A range or filter change showed the previous answer under the new heading. Pressing 7d while 30d was up swapped the title at once and left every figure below it answering the old range until the response landed — around half a second on a large database, long enough to read a number and believe it. A refresh still keeps the figures on screen, because blanking them every few seconds would be its own defect.
  • The calendar put days in the wrong weekday column. A day nobody worked has no row at all, so one missing Sunday slid every later day one column left and a Monday rendered under "S" — in a panel whose whole purpose is making "I do not work Sundays" visible at a glance.
  • "Ended · 200" counted the page, not the sessions. The list is capped at 200, and the label counted the array it received while the lifetime strip above said otherwise. It reads "200 of 431" when they differ.
  • The largest figure on the screen called itself a cost. On a flat plan it is not one: it is what the work would have cost through the API, and the qualifier saying so sat underneath in grey at a third the size. A reader on a subscription took the whole product for an accusation of waste. The figure is unchanged and now headed "Worth at API list"; anyone billed per token still sees "Cost", because for them it is the true word.
  • Every screenshot retaken, and three faults in how they are captured fixed: two first-run banners were in every frame, "set plan" was lit as an unfinished action because the capture runs against a throwaway data directory, and the feature crops had silently been failing to find their panels since the database grew.
  • The docs say what the product does now. An audit found the release check described as the only outbound call (it is one of three, all opt-in), the premium page selling a shipped feature as unbuilt, a release runbook asking you to verify a hook count that can no longer appear, and Gemini missing from every user-facing list of agents.
v0.48.02026-09-02

Every table says what its columns are

  • Three figures in a row with nothing over them is a puzzle: $7,587.14 · 12.01B · 58% reads as three unrelated numbers until you work out which is which, and the "by cost" in the corner describes the sort order rather than the columns. Every breakdown on Now, Cost and Lifetime now names them, on one line with the table's own title so it costs no vertical space.
  • The all-time breakdown moved up, under the live pulse. It was last, below a tall activity feed, so the most legible thing on the screen was the thing you had to scroll to find.
  • "Nothing measured yet" appeared while the figures were still loading. The first screen could not tell "no answer yet" from "the answer is nothing", so for the second it took the first response to arrive it announced, with confidence, that a machine with thousands of turns on it had never run anything. It says "reading your figures…" until it knows. Measured on a 641 MB database: nothing on this screen takes longer than 0.7s, so the delay was never the problem — the wrong answer during it was.
  • The answers screen showed raw Markdown. Claude writes bold text, lists and tables; the screen kept the line breaks and showed everything else as the markup it is made of — bold with its asterisks, a table collapsed into | | |. The same renderer the release notes got now covers both, and it learned tables and quotes on the way.
  • A plan you had already chosen looked unchosen. Once set, the picker's button went grey and lost its border — which is exactly what the buttons beside it look like when they are not set — so every session started with the impression that the plan had been forgotten again. It keeps its border and carries a check.
  • A Gemini error sent people somewhere they no longer need to go. "Set GEMINI_API_KEY in the daemon's environment and restart it" — but the key has a field on the Cost screen and takes effect at once. A wrong instruction is worse than a long one.
  • Errors carried a Go package name into the dialog box. agents: "/x" is not an absolute path was written for whoever was debugging it.
  • The tool table says how much each tool hands back. Calls alone told the wrong story. Bash is 46,343 calls returning 13.4 MB; Read is 11,767 calls returning 58.7 MB — four times fewer calls, four times more context. The loud tool is the cheap one, and nothing in the product said so.
  • Share a week, a month, or today — not only all time. The picker was removed once, on the reasoning that a card showing every period at once made choosing redundant. It does not: somebody sharing a working week does not want their lifetime total to be the headline, and a card that answers four questions answers none of them loudly. The choice now decides what the heading claims and which figure is lit; every other period stays on the card, because a week means nothing without knowing whether it was a normal one. It opens on the week.
  • You can see the card before you send it. The buttons said where the picture would go and nothing said what was in it: you pressed Save, opened your downloads, and only then found out. The card is drawn in the sheet now, at the size it will be seen, and it redraws as you change the period — which is what makes the choice worth having.
  • The Share button can be found. It was an outlined button in a row of 11px text, next to a solid premium button — an outline cannot win an argument with a fill. It is filled, a size up, and carries an icon. It earns that: sharing is the only thing on this dashboard that leaves the machine.
  • Copy across the product says less. Several tooltips explained the attribution algorithm where the reader had asked what a number meant, and several paragraphs defended design decisions nobody had questioned. The longest of them — the per-directory rule — was four sentences of specification on hover; it is now one. "Everything here is measured — no invented numbers" is gone from the Cost screen: readers assume the figures are real until told otherwise, so the sentence created the doubt it answered.
  • Messages about a missing claude say what to do. "Spawning is unavailable" named an internal verb and diagnosed without a fix; it now says the binary is not on PATH, that installing Claude Code fixes it, and that watching works regardless.
v0.47.02026-09-02

Pick up a session Caprock did not start

  • It never types into a process it did not launch — two writers on one terminal interleave characters and ruin both — so a session you started yourself was readable here and nothing else.
v0.46.02026-09-02

Send this week's report now

  • There was no way to find out whether a bot token and chat id were right except to wait for Monday — and when nothing arrived, that looked exactly like a week where nothing moved. A button beside the fields sends one immediately.
  • What's new showed raw Markdown. ### Fixed as a literal heading, asterisks around every bold phrase, and paragraphs broken at the eighty columns a changelog is wrapped to for the sake of diffs. It was rendered as preformatted text on the reasoning that a local-first tool should not render remote markup — right instinct, wrong conclusion, in a dialog whose only job is to be read.
  • The dialog linked to GitHub. Someone reading what they just upgraded into wants the changelog written for readers, not a tag page with build artefacts on it. It goes to caprock.dev/changelog now.
v0.45.22026-09-02

Why a weekly report never arrived is remembered now

  • The reason a send failed was held in memory, so restarting the daemon erased it — and restarting is exactly what somebody does when a feature seems broken, which made investigating the problem the thing that destroyed the evidence.
v0.45.12026-09-02

The premium lock covered the sentence explaining the feature

  • The caption and the button were centred on the panel, so they landed exactly across the middle of the text — "Off. Nothing is paused, whatever the day costs" was cut mid-word by the thing selling it. Two earlier attempts tuned the preview's opacity, which could not help: the problem was never how transparent the overlay was, it was where it sat. It is a row underneath now, reading in the order the eye already moves — here is the feature, here is what it costs — and covering nothing.
  • A gemini filter chip appeared on a machine with no Gemini sessions. Having the binary on PATH is what decides whether the New Session dialog offers Gemini; it says nothing about whether any Gemini session exists. A first run showed all · claude · gemini above an empty screen with nothing to sort by. The chip now appears when there is something to filter.
v0.45.02026-09-02

A session stays open until its process exits

  • It used to close after a stretch of silence, and every number chosen for "a stretch" was wrong for somebody: twelve hours left the day's work marked live at midnight, and one hour closed a session while its owner was at lunch. Both were guesses about a person's day standing in for a fact about a process — and the fact is available. The shim now reports the pid of the Claude Code that ran it, and Caprock already knew the pid of every session it starts itself, so the sweep asks whether that process is alive instead of watching a clock.
  • The Now screen shows now. A session that has not made a sound in two days no longer appears there, whatever its status column says — status alone cannot tell a quiet session from somebody's history, because an observed agent's sessions arrive weeks old by definition. Two days rather than one, so a session left on Friday evening is still on screen on Monday morning. Anything actually working shows regardless of age, which is what the screen is for. Older sessions are on Lifetime and under "show ended".
v0.44.52026-09-02

A session you left for lunch was closed while you were out

  • 0.44.3 cut the staleness threshold from twelve hours to one, on the reasoning that an hour outlasts lunch. An hour *is* lunch: the first person to leave a terminal and come back to it found the session gone from the dashboard. On the machine this was checked against, 44 sessions had paused for over an hour and then carried on working, and 86 of those pauses were between one and three hours — a meeting, a walk, an afternoon on something else.
v0.44.42026-09-02

Caprock now sees what a Gemini session does

  • It could start one and then observe nothing: a live session sat in the list with zero turns, zero tokens and no cost. Gemini has no hooks and writes no transcript, but it does write OpenTelemetry, and it will write it to a file Caprock names — so a spawned session now reports its turns, its tokens and its cost like any other.
  • Two of the three Gemini models still could not be used. The list was checked against the installed CLI and the pricing table, and both said these were fine. A real key says otherwise: Google has closed gemini-2.5-flash-lite to new keys, and gemini-3.1-pro-preview is barred on a free key by quota (generate_content_free_tier_input_token_count, limit: 0) — so picking either opened a terminal and then failed.
v0.44.32026-09-02

A session you were still working in could close itself

  • 0.44.0 started listening to Claude Code's SessionEnd hook so a session ends the moment it ends rather than whenever the staleness sweep next runs. But SessionEnd does not only mean "the user left": it also fires on /clear, on Escape at the prompt, and with an unhelpful other. All of them were being treated as the end. One session on the machine this was found on had been running for six days when a /clear retired it in the dashboard.
v0.44.22026-09-02

The terminal flickered on every keystroke

  • A ResizeObserver called fit() straight from its callback, and fit() writes to the element the observer is watching — so a resize could cause a resize. Idle, that settles after one frame and nobody notices. Under a TUI that repaints its whole box on every key, which Gemini CLI does, it was a visible flicker on every key. Fits are now coalesced into one per frame and skipped entirely when the geometry has not moved. It affected any terminal that repaints often, not only Gemini's.
  • Six columns of zeros above a session Caprock cannot measure. A Gemini session showed $0.00, 0 tokens, 0 turns, 0% cache, with the reason — "no hooks · no transcript" — in small grey text underneath. Zeros are how that bar says "nothing has happened yet", so it read as broken rather than as out of scope. It now says the one true thing instead: Caprock started this session but does not measure it, and the terminal below is live.
v0.44.12026-09-02

Starting a session with Gemini actually works now

  • 0.44.0 shipped the feature built against a fake binary, on four assumptions about the Gemini CLI that were written from memory rather than from gemini --help. Installing the real thing disproved all four in ten minutes.
  • A Gemini session is no longer described as a Claude one. The agent was added to the launcher and to nothing else: its sessions arrived in the list wearing no badge, with no filter chip to find them by, and the empty state called them Claude Code — because three separate places asked agent === 'opencode' and treated everything else as Claude. There is now a gemini chip beside claude and opencode, shown only on a machine that has it, and one helper decides the badge and the name so a fourth agent cannot reintroduce this.
v0.44.02026-09-02

Start a session with Gemini CLI

  • Install it (npm install -g @google/gemini-cli), and it appears next to Claude Code in the New Session dialog — same terminal, same directory picker, same row in the sessions list, same cost stream. Caprock passes the key you already entered into the child process, so it works without exporting anything in the shell you happen to be in.
  • A debounce timer outlived the screen that set it. Closing a screen inside the 400 ms live-tick window left a timer that woke up and set state on a component that no longer existed. It was visible mainly as a failed release — the timer firing after the test runner had torn the DOM down, with all 483 tests passing — which is how it got fixed twice at the wrong end before the cancel-on-unmount that it actually needed.
v0.43.02026-09-02

Claude Fable 5

  • 1 and Mythos 5.1 in the pricing table, contributed by [@SergeAx](https://github.com/SergeAx) — the first outside contribution to this repository. Cache reads on those two models cost 0.025× the input price rather than the 0.1× every other Claude row follows, which makes $0.25 look like a typo of Fable 5's $1.00; the PR pins it with a test, so a later tidy cannot quietly charge four times the real rate.
  • fetched_at in the pricing table names the most recent read, and each note carries the date for the rows it describes. One date on a table assembled from three vendors over three weeks said 18 August while holding prices read on 1 and 2 September — which tells a reader checking a figure nothing about that figure.
  • The Gemini key goes in the dashboard now. It was read only from GEMINI_API_KEY, on the reasoning that a key Caprock never holds is one it can never leak — but a daemon started at login inherits nothing from a shell profile, so switching on a paid feature meant hand-editing a launchd plist. A key nobody can enter protects nothing; the feature just goes unused. Paste it into the panel instead. The environment variable still works and still wins where both exist, and the stored key is never returned by any endpoint — the same write-only rule the Telegram token follows.
v0.42.12026-09-01

The weekly report, to your own Telegram bot

  • What moved this week against your usual, sent Monday morning — or the next day you open the lid, because a timer set for Monday 09:00 fires for nobody whose laptop is shut at weekends.
  • A setting no longer resets when you save a different one. "Set plan keeps resetting" was not a save that failed: the dashboard sent the whole settings object every time, assembled from a cache in the page, so any control could write back a field it had read minutes earlier. Two tabs were enough — change the plan in one, turn on update checks in the other, and the second restated the plan from its own stale copy. Only what changed is sent now.
  • Live activity showed a fortnight of history as what just happened. Two causes at once: the feed asked for a session's *first* sixty events rather than its last, and the store ordered "most recent" by insertion rather than by time. Those agree for a session captured live and diverge for one whose transcript was re-read, because a backfill inserts old events with new ids. On a long session every row was equally stale, which is also why it read as impossible to scroll.
  • The new-session dialog cut off the permission label — "Accept edits · asks before com…" hid the consequence, which is the one thing that label exists to state. Wider dialog, shorter labels.
  • A stray reconnect no longer crashes a torn-down page. The live socket queues a retry when it cannot connect, and that timer could outlive the document that scheduled it — harmless in a browser, where the page is going away anyway, and enough to fail a release build whose every test had passed. It now gives up quietly instead.
  • The premium chip was two controls disguised as one. The label went straight to the checkout while a chevron beside it opened the explanation, in one border with no seam between them — so clicking the word "premium" to find out what premium *is* took you to a card form. One control now: it opens the dialog, and once you have bought it names your plan and when it ends.
v0.41.02026-09-01

Ask Gemini, on your own key

  • The second paid feature: a second model inside Caprock, billed to you by Google at their prices. Caprock never stores the key — it reads GEMINI_API_KEY from the daemon's environment at the moment of the call, and it is never written to disk, never accepted by the settings endpoint and never returned by one. A key we do not hold is a key we cannot leak; the cost is that you set a variable and restart, which the panel says plainly.
v0.40.02026-08-31

A session now ends when it ends, instead of half a day later

  • The Now screen counted a whole day's finished sessions as live — "14 sessions, 0 active" — and the live pulse drew a row per known session, so a day in one repository became six identical caprock rows over six flat hairlines.
  • Upgrading Caprock no longer kills the work it was watching. Restarting the daemon — which every upgrade does — sent SIGKILL to every session Caprock had spawned. They died mid-turn, with no warning and nothing flushed: a tool that watches your work should not be the thing that eats it. Sessions are now asked to stop, waited on for five seconds so Claude Code can write out its transcript, and only then killed if they refuse. The upgrade banner also says how many sessions will close before you copy the command; sessions you started yourself are untouched, as always.
  • The live pulse shows only tracks that ran. Ended sessions are dropped, and so is any track with no events in the window — an hour of flat hairline says only that a session exists, and several of them read as a broken chart rather than as silence. When nothing ran in the last hour, the panel says that in one line.
  • A pulse row shows what its hour cost, not the session's lifetime. A long-running session printed $4,053.39 beside an hour of bars and a $101.85 day: two true numbers answering different questions, side by side. The row now sums the window the bars cover, with the lifetime on hover.
  • The new-session dialog answers its own questions. Model and permission mode both opened on an empty "default" that said nothing about what was about to run — and "default" is not a permission mode Claude Code accepts at all, so the dialog could send the binary a value it rejects. They now start on Opus 5 and "accept edits, asks before commands", with each option labelled by what it does rather than by its flag name. The worktree field and "create the directory" moved under Advanced, and the paragraph advertising OpenCode support left the dialog: every field on screen is a decision asked of someone who wanted to press one button.
  • The session timeline reads newest-first. It was the only list in Caprock ordered the other way, so the same glance meant two different things on two screens — you arrive at a timeline wanting the last thing that happened, not the first. The follow checkbox and its autoscroll are gone with it: new rows arrive at the top now, so there is nothing to chase. History stays behind "load earlier events" rather than being rendered up front, and each click now pages back from the oldest row on screen — it used to refetch the first thousand events of the session and discard nearly all of them, which on a sixteen-thousand-event session fetched the wrong end of the history.
  • "Live diff" and "Files" are one Changes tab. They answered one question between them — what did this session change — so reading it meant visiting both tabs and holding two lists in your head, and on most sessions "Files (0)" was empty besides. Files now expand independently rather than one at a time (the old accordion closed the first file when you opened the second, so two changes could never be compared), with a caret per row and expand/collapse all for reading a whole branch. Files touched but unchanged keep their own panel underneath. Old ?tab=diff and ?tab=files links still work.
  • The live diff measures a branch from where it forked. The base was always HEAD, which answers "what have I not committed yet" — so a branch whose work was committed showed *no changes at all* while the session had rewritten dozens of files. It is now the merge base with master/main: the branch's own commits plus whatever is uncommitted. The panel names its base ("since master") beside the file count. Untracked files show their contents as added lines instead of "no diff against HEAD".
  • The Projects list holds its order while you point at it. Rows are ranked by spend and refresh every 30 seconds, so a row could swap places between aiming and clicking and open a repository you never pointed at. The values keep updating live; only the sequence holds still, and only while the pointer is inside the panel.
  • Pulse rows are told apart by branch and session id. Working all day in one repository drew rows labelled caprock, caprock, caprock, most of them subtitled was responding. The branch and a short id are what actually differ, so they are what the row leads with.
v0.39.22026-08-31

Shift+Enter really does insert a newline now

  • 0.39.1 sent the right bytes and still submitted, because the bytes were only half of it: returning false from the key handler stops xterm *interpreting* the key, but the browser still delivers it to xterm's hidden textarea, which emits its own carriage return. The socket carried ESC CR and then a bare CR immediately after — [27,13] followed by [13] — and Claude Code submitted on the second one.
v0.39.12026-08-31

Shift+Enter typed \n into the prompt and sent the message

  • The newline keys sent two printable characters — a backslash and the letter n — so a message arrived as first line\n and the multi-line prompt was unusable. They now send ESC CR, which is Alt+Enter as a terminal encodes it and what Claude Code's own macOS instructions bind Option+Enter to.
v0.39.02026-08-31

The daily spend cap

  • the first thing Caprock does rather than shows. Everything else here is observation: you look and you learn something. A cap acts while you are asleep, which is when a runaway loop does its damage. Set a number for the day; when the day crosses it, the sessions Caprock started are paused.
v0.38.12026-08-30

The folder picker ran past the edge of its dialog

  • A long path — the ones under ~/Library/Application Support are the worst — made the list 738px wide inside a 520px panel, so rows spilled over the border. A grid item does not shrink below its own content unless told to, so no amount of clipping inside the picker could fix it: the containers above it had to be allowed to be narrower than what they hold. Paths now truncate with the full one on hover.
  • The picker looked like a different surface from the field above it. It sat on a transparent background with the lighter border, directly beneath an input that had neither, and the two read as two panels at different opacities. It now matches the input exactly.
v0.38.02026-08-30

Pick a folder instead of typing its path

  • Starting a session meant typing an absolute path from memory into a dashboard that is already showing the repositories you work in every day.
v0.37.52026-08-30

The button on a session Caprock did not start now says what it does

  • "Start one here" named an action and left *one* undefined — a reader could reasonably expect it to attach to, restart, or take over the session in front of them. It does none of those.
v0.37.42026-08-30

"Start a session in Caprock" now starts a session

  • It was a link to the main screen — navigation dressed as an offer, which moved the reader away from what they were doing and left them to find the real button themselves. It opens the New session dialog in place, already pointed at the repository on screen, so the one thing it promises takes one click. Asking for a working directory on a screen that is displaying it was asking someone to retype what they were looking at.
v0.37.32026-08-30

The terminal panel on a session you started yourself now says what to do

  • It opened with *"This is an externally started session — Caprock observes it but never writes into a terminal it does not own"*: accurate, and written for the people who built it. A reader saw a wall of text where a terminal should be and could not tell what was being asked of them, or whether they had done something wrong.
v0.37.22026-08-30

Buying takes one click

  • The header chip opened the dialog and nothing else, so someone who had already decided still had to read a screen and then find a price. It is now the price itself — premium $30/yr, straight to the checkout — with a separate chevron for anyone who wants the explanation first. People who are ready should not be routed through a pitch.
  • The locked preview really is legible now. Raising its opacity in 0.37.1 changed nothing visible, because a full-panel scrim was laid over the top of it: the content was brighter underneath and the sheet dimmed it straight back. The scrim is gone; the caption and the button carry their own backing instead, so the panel behind them can be read.
  • A missing plan no longer takes the header down. The chip read .url off the yearly plan without checking it was there — an older daemon, or any response without it, crashed every screen rather than hiding one chip.
v0.37.12026-08-30

There was no way to buy from the main screen

  • The only entry points to paying were the two panels behind glass, on Cost and on Lifetime — so the screen where people actually sit offered no path at all. premium now sits in the header on every screen: the one element there that is not amber, so the eye finds it when it is looking for the thing about money and not otherwise. It stops selling once a licence is active.
  • The locked preview was unreadable. At opacity-35 it was the worst of both — occupying the space of a demonstration while demonstrating nothing, so a reader could see that *something* was under the glass but not what. It is now legible through it, and the caption, which is the sales line on that panel, is larger than the body text it sits over rather than smaller.
v0.37.02026-08-30

The premium dialog, after five readers were asked to buy from it and…

  • The premium dialog, after five readers were asked to buy from it and none did. Not one of them named the price — three said $30 was nothing to them. The verdict was the same sentence in five voices: *"I am paying for delivery of something I already see for free."*
v0.36.12026-08-30

The premium dialog says the same thing in half the words

  • The paragraph above the bullets repeated what the bullets said, and the footer explained the licence twice.
  • Both prices are buttons now, and the lifetime one is brighter. An outlined second button reads as the lesser option, which is backwards: someone weighing the year should see the lifetime as the step up from it. Two filled buttons of ascending brightness make that legible without a word of copy.
  • The comparison moved under the buttons, one line each. It was a two-row table above them — "Claude Pro $20/month" over "Caprock Premium $2.50/month" — two unrelated numbers standing where a price belonged. Each button now carries what it is worth in the plan the reader already pays for: about six weeks of Claude Pro for the year, about five months for the lifetime.
v0.36.02026-08-30

Premium is ultramarine now

  • Every interactive element in this product is the brand amber, which left a subscribe button looking exactly like a range filter. Paid is a different kind of thing, so it gets a different hue — deliberately cool against a warm interface. Reserved strictly for surfaces where money is involved; an ultramarine that spread to ordinary controls would just be a second accent, and then nothing would be marked.
  • The premium dialog offers a year or a lifetime, and not a month. At $5 the monthly plan is the cheapest way to hold a licence key for a month, which is a worse deal for the buyer than either commitment beside it. It remains on the site for anyone who seeks it out.
  • The weekly report shows what would arrive, not a description of it. It was three rows reading "Sent / To / Contains", which describes an email without showing one — and behind glass that is a blurred settings screen. It now previews the message itself, with this machine's own repository names in it. Deliberately without figures: those are already free two panels down, and locking them would take something away rather than preview something new.
  • winget. winget install dspv.caprock, once Microsoft has merged the manifest for a release — the package manager already present on every Windows 10 and 11 machine, with no bucket to add first. Deferred until there was Windows demand ([ADR-014](.ai/08-decisions.md)); the first Windows user to build from source asked for it by name.
  • make build works on Windows. It did not, and the very first command in CONTRIBUTING.md failed on a fresh Windows clone: make with no sh on PATH falls back to cmd.exe, which understands none of mkdir -p, [ -d ], single-quoted -ldflags, or 2>/dev/null. It now uses the Bash that Git for Windows already installs.
v0.35.12026-08-30

The main screen's heaviest request went from 0

  • 61s to 0.001s. /v1/history answers "everything, ever" — four aggregates over the whole events table — and five components on that one screen ask for it on their own timers: the lifetime strip, the breakdown panel, the share card, the share nudge, and the screen itself. A single open tab produced bursts of identical requests and each one was computed from scratch.
v0.35.02026-08-30

Prices can now carry the dates they applied, and a turn is costed at…

  • Prices can now carry the dates they applied, and a turn is costed at the price it actually ran under. Sonnet 5 launched at an introductory $2/$10 and reverts to $3/$15 on 2026-08-31. With one row per model the only way to record that is to overwrite the figure — which would have silently restated every August turn at a price nobody was charged, growing a month's reported spend by half overnight. That is rule 6's "no invented numbers" pointed at our own history.
  • The size of the spend on the main screen, not only how it divides. Each model row carries its token count beside its cost, and the panel gained an input / output / cache read / cache write line. A share says how the money split and nothing about how much there was: "opus-5, 54%" reads the same at forty dollars and at four thousand. Output costs five times input, so the ratio is the explanation of the bill, and it was a screen away on Cost.
v0.34.12026-08-30

The premium dialog no longer disclaims its own product

  • It carried a line reading *"Not built yet"* directly above the price, which is a sales screen arguing against itself: the features are being built, the commitment is real, and hedging it sells nothing while protecting nobody.
v0.34.02026-08-29

The premium dialog now measures its price against a Claude subscription

  • Every statement on it was true and the screen still argued against itself: the loudest element was a bordered box reading *"Not built yet"*, so the first thing the eye landed on was a reason not to buy. Three prices sat where a decision belonged, and Subscribe was styled as one of three equal buttons.
v0.33.02026-08-28

Copy and paste in the terminal

  • There was none: every key went to the process, so Ctrl+C was always SIGINT and nothing could be copied off the screen. It now follows VS Code's rule, which is the one people already have in their fingers — Ctrl+C copies when something is selected and interrupts when nothing is. On macOS the question never arises, since Cmd+C and Ctrl+C are different keys. Ctrl+Shift+C/V elsewhere.
  • WebGL rendering, with a fallback to canvas. The canvas renderer repaints the whole grid; WebGL draws from a texture atlas, which is the difference between a build log scrolling smoothly and the tab stuttering. A machine without WebGL, a driver that refuses, and a GPU context lost on sleep all fall back rather than failing — a slower terminal is a cost, a blank one is a broken product.
  • Paste or drop a file into the terminal. A browser hands over an image's bytes and never a path — there is no path for something copied out of a screenshot tool — while Claude Code reads files by path. The bytes now go to the daemon, which writes them into its own data directory, and the path is typed into the session so Claude can read the file.
  • Scrollback is 10,000 lines, up from 5,000. A build log passes five thousand easily, and losing the start of what you are reading is where a terminal stops being one you can work in.
v0.32.12026-08-28

The terminal now tells the daemon how big it is

  • fit() resized the canvas and nothing else, so the PTY kept whatever size it was created with — 120×40 by default — for its whole life. Claude Code lays its menus out to the terminal size, so on any other window it drew an interface for a screen that was not there: arrow keys moved a selection nobody could see, which is what the first user reported as *"only Enter works"*.
  • Shift+Enter inserts a newline again, on a prompt with text in it. It has been wrong twice, and the second one is the interesting failure: a bare line feed (0x0A) is what Ctrl+J sends and what the documentation says works everywhere — but Claude Code reads a lone line feed as *submit*. On an empty prompt that looks like a newline, because there is nothing to submit; the moment the prompt has text, the same key sends the message. That is exactly what the user reported.
v0.32.02026-08-28

The cache hit rate now says what it means

  • It was a bare percentage, amber below 90% and identical above — so 99% and 91% read the same and neither said anything, on a figure that runs from 6% to 99.6% across real sessions. One word sits beside it: outstanding at 99%+, good from 95, ok from 85, low below. The bands are set against the observed spread rather than picked for roundness, so *outstanding* lands on about one session in nine and is worth reading.
  • The share card asks the reader what theirs is. It carried a figure and a domain and stopped there — someone seeing another person's total had no reason to think it was a thing they could do too. A question, not an install line: a command on a picture is an advertisement and reads as one, and the domain in the heading is where a person who wonders goes to find out.
  • Dismissing a banner recorded the wrong time. The premium banner, the premium hint and the share nudge all stamped the dismissal with a fresh Date.now() while every other decision in them used the clock they were handed. The two disagreed by however long the render had been on screen — invisible in production, but it meant the test suite passed for a year and then began failing on the day the fixed test clock and the wall clock crossed, with nobody having touched the files. All three use the clock they are given, and the boundary is now tested to the millisecond.
v0.31.32026-08-27

Every way of asking for a newline now works in the web terminal

  • This shipped supporting Shift+Enter alone, and the first user to want a second line reported back that Option+Enter was the one he pressed — which is what Claude Code's own macOS documentation tells people to enable. Claude Code accepts four ways of asking for a newline and a person reaches for whichever one they learned elsewhere, so all four are handled, each sending exactly what Claude Code expects over the PTY: Shift+Enter sends CSI u, Option+Enter sends ESC then CR, Ctrl+Enter and Ctrl+J send a line feed, and backslash-Enter needs nothing from us.
  • A line under the terminal saying how to get a newline. Shift+Enter had worked since v0.30.1 and the user who wanted it still could not find it: he pressed Enter, watched half a thought get submitted, and concluded multi-line prompts were not possible. A feature nobody can discover is not shipped.
v0.31.22026-08-27

Homebrew reported "already installed" for a release that was already out

  • A tap is not served by the Homebrew API: it is read from a local git clone that brew upgrade refreshes only through auto-update, which runs at most once every 24 hours. A user who ran any brew command earlier the same day was told 0.31.0 already installed hours after 0.31.1 shipped — and the command Caprock itself had given them appeared to be broken. Every place that names the command now says brew update && brew upgrade caprock, with a test pinning the reason so it does not get "simplified" back.
  • Hovering "Save the image" now shows something. It changed only its border colour — one step of grey against a dark panel — so pointing at it looked identical to not pointing at it. A control the eye cannot confirm it is on reads as disabled. It fills instead.
  • "Quick chat" and "+ New session" line up. Quick chat sat inside a wrapper div next to a bare button, which made them two different boxes in the same flex row. The error message that wrapper existed for is positioned absolutely now, so a rare failure cannot change the height of the row.
  • The version chip tells you how to update, in steps. It used to print one line — the command for however the daemon guessed this copy was installed — and stop, which answers only the first of three questions: run what, then what, and how do I know it worked. It now shows tabs for macOS, Linux and Windows with the routes each one has, the way this copy appears to be installed opened first, and three numbered steps with their own copy buttons: update, restart, check. The platform choice is remembered.
  • "What's new" beside the version. The published release's own notes, in a dialog, without leaving the dashboard. The text arrives with the version in GET /v1/update — the same GitHub response, so no extra request and no further exposure — and it is rendered as text, never parsed as markup.
  • Release notes come from CHANGELOG.md, not from commit subjects. The dashboard shows that text under "what's new", and a generated list of 1c836d5: fix(ui): … lines is not something anyone reads. A version with no changelog section now fails the release build rather than shipping an empty "what's new".
  • The share dialog's guarantees are two bullets, not a paragraph. A reader scanning for what does and does not leave their machine should not have to read a sentence to find out. Bigger type, one claim per line — and pinned by a test, because this is the copy that gets reworded whenever the dialog is tightened.
v0.31.12026-08-27

Sharing the card produced two images; saving it produced one

  • That split was the whole diagnosis — the download path draws once and writes one file, so the duplicate could only come from the native share, which handed the OS {files, text}. Two payloads leave the receiving app to decide what they mean, and macOS's Copy resolved it as two items. The card already carries every figure and its caveat, so the caption was never load-bearing; the file now travels alone.
  • A second way to get two cards, fixed in the same change. One flag both labelled the share button "Drawing the card…" and disabled it. Clearing it early — so the label would stop lying while the OS share sheet sat open — also re-enabled the button underneath that sheet, and a second press drew a second card. Labelling and locking are now separate states.
  • The version chip is a button, and it tells you how to update. It used to say a newer release existed and stop there, which answers the half of the question nobody needs help with. It now opens a dialog with the running version, what is published, a check-now, and the exact command for how this copy was installed — brew upgrade caprock, go install …@latest, or the release page for a downloaded binary — with a copy button.
  • make reload builds the dashboard and binaries, installs them over whatever copy is on PATH, and restarts the daemon. Testing a UI change against a running daemon took four commands and getting one wrong meant looking at a stale build and drawing conclusions from it.
  • Plan limits moved into the Today row. They had a full-width panel holding two percentages, three rows above the money, which made a reference figure look like a headline — and when both windows were stale, spent a band of the screen explaining that the figures beside it meant nothing. Now one cell beside burn, sessions and cache hit, leading with whichever window is closest to its limit, since that is the one that will stop the work.
  • The share button in the header is visible. Grey 11px lowercase between "feedback" and the build label — invisible enough that the person who commissioned the feature could not find it on his own dashboard.
  • The premium banner says what the feature does. It read "Premium stops a day that runs away from you, and alerts before a plan window does" — a metaphor for a mechanism, and the verdict on it was "непонятно". A banner has one line to name something the reader can picture, so it now says what the modal behind it goes on to explain: sessions pause when the day crosses a limit you set.
v0.31.02026-08-27

The share card looks like the dashboard, not like an advert

  • What people post is a screenshot of the thing working; the old card was a poster with one enormous number on it. It now carries eight tiles — today, the week, the month, all time, the daily average, tokens, cost per million and the cache rate — over two breakdowns: where the money went by model and what it went on by kind of work, each with a percentage beside the figure.
  • The share button can be found. It was the word "share" in 11px grey in the header, between "feedback" and "status". It is now an accent-bordered button in the ALL TIME panel, beside the figures it offers to post, and it is always there — whether someone's numbers are worth posting is their call.
  • A nudge when there is something worth saying, separate from the button. Three occasions, each reaching a different kind of user: a round number crossed, the first full week of data, and a week clear of every week before it. A record needs to beat the previous best by a fifth — while usage grows, "highest ever" is true nearly every week, and a prompt that fires every week is one people stop reading. At most once a week, dismissible.
  • Thin bars on the share card were drawn as hooks. A corner radius wider than the bar itself: haiku at $0.59 is two pixels beside opus at $5,338, and 3px rounding turned it into a squiggle.
  • The card had lost its caveat while being rewritten. A dollar figure posted without "not a bill, and not money saved" reads as a bill somebody paid.
  • Model ids from a gateway carried a slash — minimax/minimax-m3 — which on an image about to be posted is indistinguishable from a repository path.
v0.30.12026-08-27

Shift+Enter in the terminal, for prompts longer than one line

  • A terminal cannot tell Shift+Enter from Enter — both are carriage return, ASCII 13, and have been since the teletype — so typing a numbered list submitted the first line and threw the rest away. Terminals that support multi-line prompts send CSI u instead (ESC [ 13 ; 2 u), which is what Claude Code listens for; xterm.js does not send it by default, so Caprock now does. Reported by the user who noticed it works in a normal terminal and not here.
v0.30.02026-08-27

Paid features unlock from a licence key

  • A string carrying its own expiry, pasted into settings, checked on the machine — no signature, no online validation, no machine binding ([ADR-022](.ai/08-decisions.md)). The binary is Apache-2.0, so any check is deletable in five minutes; what an offline check buys instead is that a paying customer's features cannot fail because of a plane, a proxy, or a server of ours being down. Seven days of grace after expiry, announced in words, because a late renewal is a bank's timing rather than a decision to stop paying.
  • GET /v1/premium reports the licence state alongside what the plan costs, so every surface that mentions the paid version reads one answer.
  • A lifetime purchase, $100 once. An ordinary key with a distant date rather than a "never expires" flag: a flag would need a second code path in the daemon, and the design is one path.
  • caprock license — show, set, clear, and issue keys from the terminal. Issuing exists because the Stripe webhook was the only thing that could mint one, which left no way to serve a customer who paid another way, a refund reissued, or a friend. license set refuses a key that will not work rather than storing it and leaving someone to wonder why nothing happened.
  • Every paid feature is visible in the product, in the place it will occupy, behind glass, with one click to paying. Paywall.test.tsx enforces the rule that makes this honest rather than hostile: a lock may only cover a feature that does not exist yet, and never a panel showing measured data.
  • Third-party models are priced for everyone, and Premium lost a feature. DeepSeek, MiniMax and OpenAI arrive through OpenCode as usage nobody could cost — $155 of the author's own spend sat outside his total. Adding the providers' published prices was cheaper than building a paywall around them, so providers is no longer a paid feature: charging for something the free version performs is how a paid tier becomes a hostage.
  • The unpriced warning fired on turns with nothing to price. It tested for tokens being present rather than greater than zero, so turns recorded with explicit zeroes were reported as usage outside the total. The dashboard was telling users money was missing when none was.
  • The same model reported by two routes was two rows, one unpriced: minimax/minimax-m3 from OpenRouter and MiniMax-M3 from the direct API. Gateway vendor prefixes are stripped.
  • A quick chat was labelled 2026-08-26-212735 — the directory name that keeps two chats from colliding, shown as an identity. It reads chat · Aug 26, 21:27 now, and a migration renames the ones already stored.
  • "Stuck in a loop … open" landed on the session's default view. For a loop three hours old that is the wrong end of a long list. It opens the timeline at the moment the repetition started.
v0.29.02026-08-26

Share from anywhere

  • A share control in the header, on every screen, offering the week, the month or all time. Where the browser supports it the image goes straight to the operating system's share sheet; where it does not, the card is saved and the post opens with the text already written — because a browser cannot attach a picture to a tweet, and a button that appears to post and does not is worse than one that says so. The card carries caprock.dev, so the link travels with the image.
  • Paid features are shown where they will live, locked. The daily spend cap occupies its real place on the Cost screen, behind glass, with your own figures under it and one click to paying. Inert and marked: a preview that responds to a click is a preview that lies.
  • The paid version is mentioned on Now. It was deliberately kept to Cost and Lifetime to avoid interrupting anyone at work, which meant a user with no loop and no runaway session never learned a paid version exists at all.
  • The ALL TIME panel was visibly slower than everything around it. GET /v1/history?range=all took 0.76–1.17s against 0.15s for the rest of the screen, and ToolDistribution was 60% of it: the index it matched carries (kind, ts) but not tool, so ~80k rows were read from the table for one column. A covering index makes the same plan covering — 139 ms to 46 ms warm, and the cold figure was 1780 ms, which is what a person actually waits through. Migration 0016.
  • The landing page can be installed from. Its final call to action offered caprock up — the command you run *after* installing — so a reader who scrolled the whole page to decide yes was handed the one instruction that does nothing on a machine without Caprock on it. It now carries the real install, defaulting to the visitor's platform.
  • The terminal has a strip of its own. It is the reason the first full-time user could drop the Claude Code IDE, and the page had never mentioned it.
  • The teams page is three sections instead of ten: what you get, where it runs (your VPC — counters leave a machine, content never does), book the call.
v0.28.02026-08-26

Quick chat

  • A session with no repository: click and start asking. The spawn dialog demanded an absolute path before it would do anything, which is the right question for work and a wall in front of "look this up for me". Caprock makes a directory per chat under <data_dir>/chats/. One per chat, not one shared folder — Claude Code keys a transcript by working directory, so a shared one would collapse every conversation into a single project row with a single transcript.
  • create on POST /v1/agents, and a checkbox for it in the spawn dialog: starting a new project no longer means leaving the dashboard to make the folder in a terminal. Opt-in, one level deep, under a parent that already exists — a typo in an absolute path fails loudly instead of materialising a chain of directories somewhere you have never been.
  • Plan-limit alerts. A warning at 90% of a 5-hour or 7-day window, high severity from 95%, with the reset time. Not at 85%, where the Cost screen turns amber: an alert that fires wherever a colour changes is one people learn to scroll past. A window whose reset clock cannot be believed raises nothing, because an alert built on a stale reading would never clear.
  • A share offer on a rhythm — weekly and monthly, drawing the same card over those periods. It previously went loud only at a money milestone, so anyone whose spend never lands on a round number was never actually asked. The offer says what the image contains before anyone clicks: totals only, no project names, no paths, no prose, saved locally, uploaded nowhere.
  • The paid version is mentioned in the dashboard, for the first time: a line on Cost and Lifetime that opens with what a day costs on this machine, and a note beside a loop or a session that spent a lot for nothing. Clicking either opens a dialog about that feature, with the price and both a way to read more and a way to subscribe. Never on Now, never over your work, never on an empty dashboard, and dismissible for a month.
  • GET /v1/premium — what the plan costs, served by the daemon so no price is hardcoded in the UI. It ships in the binary either way (rule 4 forbids fetching it), but it lives once in Go, and a test reads the site's pricing file and fails when the two disagree.
  • The terminal rendered every glyph in the fallback font. xterm.js paints to a canvas and was handed the literal string var(--font-mono), which a canvas context cannot resolve — invisible in Latin, unreadable in Cyrillic. Two more defects in the same place: the character cell was measured against the fallback and never re-measured, and no subset was ever fetched, because subsets load when a matching character enters the DOM and canvas text never does. All six subsets JetBrains Mono ships are now requested by name.
  • One directory counted as two projects. Claude Code records repo_root only when it resolves a checkout, so a session started where git could not answer got its own row — labelled with its full filesystem path, since a label is derived from the path when there is no project name.
  • Plan limits were on a screen nobody looks at for them, and the panel vanished entirely when there was no data, so the one place that could have explained the absence showed nothing. They are now also a line on Now, and the panel says where the data comes from when it has none.
  • The control that starts a session sat at the bottom of Now in 11px grey, beside a checkbox and a timestamp. It is at the top, at the size of an action.
  • Screenshots leaked the machine's directory layout. The scrubber renamed projects but not the directories above them, and the Cost screen prefixes parent segments when two checkouts share a basename.
v0.27.42026-08-26

Hooks never fired on Windows

  • Claude Code runs command hooks through bash, which reads each backslash as an escape, so C:\Users\…\caprock-hook.exe reached the shell as C:Users…caprock-hook.exe and every hook failed. The dashboard still filled from transcript tailing, so nothing looked broken — the only symptom was a "command not found" line in a Stop hook.
v0.27.32026-08-26

Half of the Windows hook fix, superseded by 0

  • 27.4 an hour later. It quoted the shim path, which stops a space splitting the command but leaves backslashes to be eaten by the shell — so hooks still failed on Windows. Upgrade past it.
v0.27.22026-08-26

The lifetime breakdown is its own panel, between the activity feed an…

  • The lifetime breakdown is its own panel, between the activity feed and the session rows. It spent two releases in the wrong place: hidden inside the all-time line nobody found it, and opening it there pushed Today and the live pulse below the fold. Both were the wrong position rather than the wrong default — these are lifetime figures, not something to check between glances at the pulse, so they sit below the live panels where the screen has room and nothing competes with them. The all-time line is one row again.
v0.27.12026-08-26

The tool and model breakdown was hidden behind something that did not…

  • The tool and model breakdown was hidden behind something that did not look like a control — a toggle styled like the muted caption beside it, same size, same colour, no border, so it read as text and went unfound.
v0.27.02026-08-26

The tool and model breakdowns open on Now

  • The all-time line offered them behind a link to another screen, which is a strange thing to do with data already in the response that drew the line. Most-used tools by calls and where the money went by cost, expanded in place; the Lifetime screen keeps the full tables. Closed by default, since the total is the point of the line and this screen is mostly live panels.
  • A spend cap is offered beside the figure that argues for it. Someone reading their own lifetime total is, at that moment, the person most likely to want a limit on it. It stays a link at the weight of the one next to it — a dashboard is not a checkout, and the price lives on the page it points at.
  • Sessions are full-width rows rather than tiles. Three to a viewport made a session's own figures reference material, small enough that you read the project name and moved on. A session is the unit this screen is about, so each one now gets the width and its numbers come up to the size of the ones in Today.
v0.26.02026-08-25

The all-time total, on the screen you keep open

  • A hundred and twenty-nine sessions across fifty-eight days at ten thousand dollars of usage is the figure people repeat, and it lived two clicks away — so someone who opens the dashboard daily could go weeks without meeting it. It now rides above Today as one line: the total, the three figures that make it mean something, and a link to the rest. Deliberately a summary rather than the screen copied upward; the tool table and the model mix are a screen's worth of reading and stay on their own.
  • What the money went on, without leaving Now. "Which repository" and "what kind of work" are two halves of one question, and the second half only existed on the Cost screen. The projects panel now carries both, on the range already chosen there and from the summary it already fetches — no second request, one control for both answers. It renders nothing when the linkage behind it is too thin to mean anything: on a today range most tool calls have not yet been attached to the turn that paid for them, so a bar drawn from that would describe a gap in the data rather than the work.
  • The History tab is now Lifetime. It promised a log and held a total — the panel inside it already called itself Lifetime, and the tab now says the same. Renamed in the docs and in the screen name feedback issues carry.
  • Every screenshot recaptured, showing the calendar view, the header's active-tab pill, the plan-value tiles and the work-mix strip — a day of work none of the published images had.
  • A machine with one busy session and one idle one wasted a screen of space. Each state opened its own three-column grid, so "Active · 1" took a row and left two thirds of it empty, then "Idle · 1" did the same below. The cards now flow through a single grid with each run's label above its first card; grouping reads the same and stops reserving a row per group.
  • The screenshot anonymiser was written and never called. Every published image had been anonymised by hand instead — which happened to work, and would have stopped working the first time someone forgot. It now runs before the capture and a failure aborts it. It also rewrites the activity feed's text, which the path rewriting never touched: the feed prints whatever the sessions actually did, and on a working machine that is client material. Only five fields reach the screen (internal/narrate), so those are replaced and the rest of the payload is left alone.
v0.25.12026-08-25

The header said nothing about which screen you were on

  • The active tab was a bg-panel-2 tint against the header's own bg-panel — a few percent of lightness apart — with its label one step of grey brighter than the rest, so on open there was nothing to read. It is now the filled accent pill the agent filter already uses: a solid block of colour is recognised before any text is. The other labels move up to full strength from the muted grey the whole row had been sitting in.
  • A colour class on a link was silently ignored. a { color: … } carries the same specificity as a text-* utility and was declared after them, so it won on order — the first attempt at the tab above rendered amber text on an amber pill, and the same trap waited for any link anyone tried to colour. The default is now at zero specificity, so a class always takes over.
  • The feedback dialog asked three questions before you could type one. "Something is broken / missing / unclear" put three sentences in front of the box, and at a glance the buttons were near-identical because the distinguishing word came last in each. One word each now — bug, feature, unclear, other — with the catch-all there so nobody stalls deciding which of three a thought belongs to.
  • "One sentence is enough" was set in the same faint grey as the fine print below it, so the line that decides whether anyone writes anything at all read as a footnote. The note under it now says the same thing in one line instead of three.
  • The footer's premium link was unreadable and unclear. "what should paid add?" was too quiet to see and said nothing about where it led; it reads premium now, at a weight that leaves the team line as the only offer on the row.
  • An install prompt for the agent you already have open. Everyone installing Caprock has Claude Code running in a terminal, and pasting eleven lines is less work than deciding whether you have Homebrew and what Windows does instead. Not a second install method — underneath it is still brew, scoop or a release binary. It names its sources rather than saying "install caprock", since an unrelated package of the same name sits on PyPI, and it says caprock up --yes: consent for the hook is refused rather than assumed when stdin is not a terminal, which is what an agent's shell provides, so without the flag the install completes and the dashboard never sees a session. In the README, on the site, and in /install.md.
v0.25.02026-08-25

The month reads as a calendar

  • A week per row, one square per day, shade carrying cost — so the rhythm of how you actually work is visible without reading a figure. Thirty bars in a row answer "how much on the 14th" and hide "I do not work Sundays". Shades are cut on a square-root scale, because a few heavy days set the maximum and linear buckets would leave every ordinary day in the palest step, making a busy month look empty. The bars remain, one click away, for when the amounts are the question.
  • The daily bars have a scale. They are normalised to the tallest day, so without a labelled line a bar's height meant nothing and a $20 day could not be read against a $200 one. Two or three rules, rounded to a 1/2/5×10ⁿ step: a line labelled $237.83 measures one particular day rather than serving as a ruler for the rest.
  • The live pulse was empty after a daemon restart, while agents were working. Stopping the daemon ends every live session, and ended was permanent — the session row kept it whatever arrived afterwards — so a working agent stayed marked ended until it happened to start a new session. Anyone upgrading Caprock hit this, on the one screen whose entire job is showing what is happening right now. ended is now sticky only against events no newer than the one already stored, which keeps a finished session's transcript from resurrecting it while treating a session that is still emitting as what it is.
  • The plan-value panel wasted most of its width and buried its own headline. Two columns on a full-width panel left two thirds of the row empty, and the multiple — the figure the panel exists to deliver — was set as a word inside a sentence at body-text size. Three tiles now read left to right as their own sentence: you pay this, the same work costs that, which is this many times over.
  • A model's snapshot date broke the column it sat in. claude-haiku-4-5-20251001 was the only name that wrapped to a second line; the trailing date is now elided to with the full id on the row's tooltip, where someone reconciling against an invoice can still read it.
  • Tile captions sat at different heights across a row. One caption wrapping to two lines dragged its neighbours off the shared baseline, so they read as text stuck to the underside of a number rather than as a row.
  • Spend from a filtered-out session appeared under the other agent. The projects list has an "orphan" row for spend whose session was deleted; under a filter, excluded sessions looked deleted, so that row collected the other agent's money and showed it unlabelled.
  • Submissions could be lost silently. Both /api/waitlist and /api/feedback fired their notification and never read the response: a missing chat id, a network error and a 403 from a revoked token were all indistinguishable from success. Team leads now record the failure onto the stored record; feedback, which has no store behind it, returns 502 so the form can offer the email address instead of thanking someone for a message nobody received.
  • The activity feed told OpenCode users to start claude when it was empty under an OpenCode filter.
v0.21.02026-08-25

An agent switch on the Now screen

  • all / claude / opencode, in the middle of the Today header, on a machine that runs both. It applies to the whole screen — today's totals, the live pulse, the activity feed, the projects list and the session cards — because a filtered list beside an unfiltered total is how a reader ends up quoting a number that means something other than what the heading says. Totals filter server-side via GET /v1/stats/summary?agent=; an unrecognised value is a 400 rather than everything. Rows from the second agent carry a small oc mark.
  • caprock status reports the OpenCode reader. A machine running both shows those sessions mixed together, so "no OpenCode sessions yet" and "OpenCode is not being read at all" looked identical — and someone who upgraded for the feature had no way to confirm it was working.
  • Per-directory cost was silently empty for OpenCode sessions. touch_dir is derived from the event payload by the store rather than trusted from the caller, so that no writer can supply a hand-made value; the OpenCode ingester emitted its own field names and every tool call was stored unplaced. The payload is now shaped like a Claude Code hook payload, which also makes work-kind classification and narration work unchanged.
  • Caprock's pricing table was applied to unpriced OpenCode turns. The suppression relied on a cost already being present, so a turn OpenCode had not yet priced acquired a figure from different arithmetic — one column holding two costing methods, with nothing on screen to say which produced a given row. No event sourced from OpenCode is priced by Caprock now.
  • Caprock now watches OpenCode sessions too, on the same screens. A machine that runs both agents had its spend split across two tools that each saw half of it; sessions from either now share one stream, tagged with which agent produced them, and project cost sums across both. OpenCode keeps its own SQLite database with cost, tokens, directory and model already computed, so the import needs no shim, no settings file to modify and no transcript parsing — and its cost is carried across rather than recomputed, because two arithmetics over the same tokens would disagree about one session's total. The database is polled every five seconds and opened read-only; events are keyed on OpenCode's own identifiers, so re-reading a session stores nothing. Verified against a real installation: 70 sessions, 19,236 events and $156.28, matching the source exactly. Live streaming and session control are not built (.ai/16-opencode.md).
v0.20.12026-08-24

The Projects panel grouped by directory, not by repository

  • The label was the basename of the session's cwd, so one repository showed up as several rows (caprock and ui), a subdirectory posed as a project (app under amarketer), Caprock's own agent worktrees became projects (worker-1), and two unrelated paths ending in the same segment were silently summed into one row — on the owner's own database, two different testrepos and two different repos. A row is now the repository a session's cwd belongs to, resolved by walking up for .git (following a linked worktree to the repository that owns it) once per directory at ingest, and stored on the session so historical rows keep a stable label even after their directory is deleted. Existing databases are backfilled on first open.
  • The live pulse's cost tiers were indistinguishable in the light theme. The bar colours were hardcoded rgba lifted from the dark palette, so on a white panel around it and well above it composited to a contrast ratio of 1.05 — the same orange to any eye — and the legend named a distinction a light-theme viewer could not see. Every bar was also nearly invisible there (1.46–1.68 against the panel), and the idle hairline sat at 1.13. The tiers now resolve through the design tokens the rest of the dashboard already uses, each with its own alpha, so each theme supplies its own hue: on dark the tiers climb in brightness, on light they climb in depth, because light's --color-accent-strong is darker than --color-accent rather than lighter. Measured from the rendered canvas in both themes, the worst adjacent-tier separation is now 1.93 in light (from 1.05) and 1.59 in dark, with every tier above 1.9 against its panel. The dark theme keeps its palette and its reading order.
  • A theme switch left the pulse painted in the old theme's colours. The canvas repaints on data change and on resize, and the colours are read from the stylesheet at paint time, so flipping the theme recoloured every panel except the one whose colours had just changed — until its next event arrived. It now repaints on the theme attribute too.
  • The pulse legend could drift from the bars it explains. The swatches were a second hardcoded copy of the tier colours, and had already drifted: the idle chip was drawn at alpha 0.35 while the canvas drew the hairline at 0.16. Both now come from one definition, so the key cannot say something the chart does not do.
  • caprock report prints your measured usage in a form you can publish. The figures behind the launch numbers were hard-coded constants read on one day, which is the one failure mode a page premised on honest numbers cannot survive: they go stale silently, and nothing says so out loud. The command re-reads the same measures from the same capture — total at API list prices, the plan multiple, turns, sessions, projects, the cache hit rate and what the cache cut, the date window with its active-day count, the top projects and models, and the pricing-table version — so a published figure can be regenerated rather than remembered. Three shapes: a block for a post, --markdown for a README or anywhere that renders markdown, and --json shaped to regenerate the site's facts block. It reads the running daemon over the existing GET endpoints and issues no writes; with the daemon down it says so rather than opening the database behind its back with a second copy of the aggregation SQL.
  • The report carries its caveat inline, on every shape. The number is large and has a dollar sign, and shown without a qualifier it reads as an amount owed — the same misreading CostBasis was built for on the dashboard. The wording is the dashboard's own rather than new phrasing, so the CLI, the dashboard and the site say the same thing: on a flat plan it is what the same work would have cost through the API, *not a bill, not a discount received, and not money back*; on metered billing it is approximately the actual cost and never a saving; with no plan stated nothing is claimed at all. The caveat is the second line rather than a footnote, so quoting the headline visibly cuts something off.
  • The multiple is refused rather than guessed when there is no fee to divide by. A flat plan with no monthly price stated has no denominator, and the report says so instead of printing a number. It is also computed against the plan fee prorated to the measured window — the same arithmetic PlanValue uses — and prints that fee alongside it, so the division can be checked rather than taken on trust. Comparing a five-week window against a single month's fee would have understated it by a quarter.
  • The report carries the raw token counts the cache percentages are computed from — cache-read against fresh input, one line in the human output, their own fields in --json, their own rows in --markdown. On the owner's data that is fifteen billion tokens read from cache against a hundred and twenty-eight thousand fresh, which is the concrete half of the cache story: "99% cache hit" is an abstraction, the two counts side by side are a picture a reader who does not think in percentages can hold. They are published as counts and nothing else — no ratio between them is derived anywhere, because the two percentages already state what the cache did and a second derived number would leave a reader working out which one is the real claim. The caveat is unchanged: this adds detail, not a claim.
  • The prorated plan fee prints with cents. It is the denominator of the multiple beside it, and the whole reason it is printed is that the arithmetic should check out on sight — but $233 rounded from $233.33 made a reader's own division come out at 41.6 against a printed 41.5. Cents cost two characters; a footnote explaining the rounding would have been more noise than the rounding it explained.
  • A new or nearly-empty database produces an honest sentence, not a wall of zeroes. Day one has no window to describe and nothing to divide, so the report says nothing has been recorded yet and stops. Figures that cannot be measured are absent from --json rather than sent as 0, because a consumer cannot otherwise tell a real zero from a missing value — a 0% cache hit rate reads as a broken cache, not as an absence of data.
  • The task runner can be turned on from the dashboard, without restarting the daemon (POST /v1/hive). The Tasks screen's off state used to hand over caprock up --hive ~/caprock-tasks to paste into a terminal, which meant the one control the feature needed did not exist in the product. The button opens and seeds the queue directory, starts the board and wires the orchestrator on the running process, and the board replaces the off state in place. It confirms first — naming the queue directory and the repository, and saying that Caprock will be able to spawn Claude sessions with permission prompts skipped — because a silent one-click that begins spawning agents is not something a local-first tool should ship. GET /v1/status now carries suggested_hive and suggested_repo while the runner is off, so the confirmation names real paths rather than a placeholder.
  • The empty task board says which button comes first. Six empty columns and two buttons do not, and starting the orchestrator over an empty board does nothing. The line appears only while there is no task at all.
  • Each project row expands to a per-directory breakdown — what ui cost of caprock's total — because "which part of the monorepo is burning the budget" is the question a per-repo number raises and cannot answer on its own. A repository whose work happened in a single directory has no breakdown, since it would restate the row's own total.
  • The breakdown is charged by which files Claude touched, not by where a session was started. In a monorepo you open the repository root and let Claude edit across /services/api and /services/web; you do not start a separate session in each. Grouping by the session's directory therefore answered "where was the terminal" — and on a real database only one repository expanded at all. Every repository now expands, with rows like /services/api and /internal/store.
  • A turn counts toward the directory of the most recent file it touched, and work keeps counting there until it touches a file somewhere else. Work happens in stretches: you say "finish /app", and Claude edits a file, runs the tests, reads the output, greps, edits again. That whole stretch is work on /app. Reading, editing or writing a file counts as touching it; running a command does not — so the commands, tests and searches between two edits count toward the directory being worked on rather than falling out of the answer. Each turn's cost goes whole to one directory, never split between two, so the rows still add up to the repository's own total exactly.
  • Each directory row shows its tokens, its share, and its cost. The share is of the whole repository, including the two rows that are not directories, so the column adds up to 100% and nothing is hidden in the denominator. Shares round down, and a directory with real but very small spend reads <0.1% rather than 0%.
  • Work whose files live outside the repository gets its own row, labelled *"outside the repository"* — Claude's notes on the project, agent scratchpads, test-output directories, or another checkout. It is real work on the project and counts toward its total, but it happened outside the tree, so it is not charged to any directory inside it.
  • The Tasks off state is three steps and a button, not three paragraphs. It carries the same three facts that decide whether anyone would run this — the work happens in a separate git worktree, *Caprock* runs the checks rather than the agent, and the queue directory is created new — but as a numbered strip rather than prose, because nobody reads a wall of text to decide whether to try a button.
  • Per-directory attribution was rebuilt after the first rule proved useless in practice. The original rule charged a directory only when *every* file a turn touched was in it, which was exact and answered almost nothing: on a real 191k-event database it put 87.6% of one project's $1735 into "repository-wide work". Asking what a service costs and being told "we could not tell you" for seven eighths of the money is not an answer. The rule now carries forward from the last file touched, and the same project reads /app 61% ($2090.67), /.ai 6.8%, /app/tests 2.8%. Nothing is split or estimated — each turn still goes whole to one row — but the row it goes to is now decided by a stated rule rather than by whether the turn happened to contain a file edit.
  • "Repository-wide work" now means one narrow thing: the opening turns of a session, before Claude has touched any file. It is usually nothing at all, and the row is omitted entirely when it cost nothing rather than showing a puzzling $0.00.
v0.20.02026-08-24

Expanding a repository now gives a tree instead of a flat list of ful…

  • Expanding a repository now gives a tree instead of a flat list of full paths. It was 43 rows on a real database with nothing showing that /ui/src/components and /ui/src/lib both live under /ui; it is eight, each opening to what is inside it, three levels deep. Spend below that rolls into the deepest row shown and says so, so the parts still add up to the repository total.
  • The two rows that are not directories — work outside the repository, and work that belongs to no single one — read as part of the same table now rather than as italics from somewhere else.
  • Clicking a directory row could land you in an unrelated session. The row was never a link: expanding a repository grew the panel past the activity feed beside it, and the click landed there instead. The tree removes the overshoot, and breakdown rows are now explicitly inert.
  • Plan value spent the right half of a full-width panel on nothing, and its multiple was aligned to the first line of a two-line sentence so the second dropped below it. Plan limits had no padding and ran into its own border. The feedback dialog was sized like a table of figures rather than a box you type a paragraph into.
v0.19.02026-08-23

The Cost screen shows what the money was spent on

  • commands, code edits, reading, MCP tools, or turns that called no tool at all — beside the existing splits by model and by project. caprock report carries it too, and withholds it rather than guessing when too many tool calls cannot be linked to the turn that paid for them.
  • Tool calls that name no file — Bash above all, the most-used tool there is — were never linked to the turn that paid for them, so their spend was reported as "no tool call". On a real database that meant 86.5% of a month attributed to turns that called nothing, where the true figure is 9.4%. The links are recovered on first run, in the background, and verified against the transcripts rather than guessed; a call whose transcript is gone stays unlinked.
v0.18.02026-08-23

A task-runner worker could write files anywhere on your machine

  • Mailbox delivery built its destination from a field inside a message file without validating it, so a message addressed to ../../../x wrote above the queue directory. The author of those files is a Claude session running with permission prompts skipped, so a confused worker reached this with no attacker involved. Validated on both ends now, contained to the queue directory as a second layer, and a refused message is quarantined rather than dropped.
  • Task ids reached the filesystem unvalidated, so a hand-written task file could read or write outside the queue directory.
  • caprock could destroy commits on your own branches. Creating a worker's git worktree force-reset a branch of the same name if one already existed. Worker names are predictable and nothing cleaned up after a run, so a second run dropped the first one's commits off the branch tip. It now reattaches to a worktree it already owns and otherwise refuses, naming the branch.
  • Granting folder trust rewrote the whole of ~/.claude.json to set one field, losing its key order and rounding any integer past 2^53. It now preserves the file, records only the grants it made, and caprock hooks uninstall revokes them.
  • The settings.json backup was taken once and never refreshed, and nothing could restore it. It refreshes when the file has genuinely changed, keeps the oldest snapshot plus the newest few, and caprock hooks restore exists.
  • Retention pruning could have deleted every event if retention were ever set to zero at runtime.
v0.17.02026-08-23

caprock report prints your own measured usage in a form ready to pu…

  • caprock report prints your own measured usage in a form ready to publish: what it would have cost at API list prices, against what your plan costs for the same window, with the caveat inline so it cannot be quoted without it. --json and --markdown for the other places you would paste it. The plan fee is prorated to the window and printed with cents, so the division is checkable on sight rather than asserted.
  • In the light theme the pulse's "around it" and "well above it" tiers rendered at a contrast ratio of 1.05 — the same orange — so the legend explained a distinction a light-theme reader could not see. The tier colours were copied from the dark palette instead of reading theme tokens; the legend swatches were a second copy that had already drifted from the canvas they described.
v0.16.02026-08-22

Any page open in your browser could reach the API while the daemon wa…

  • Any page open in your browser could reach the API while the daemon was running. The cross-site check trusted a request that carried no Origin header, and browsers omit it on cross-site simple requests — a form post, or fetch with text/plain. Behind that check, unauthenticated, sat an endpoint that takes a command from the request body and runs it. The check is now layered — Sec-Fetch-Site, Origin, Host, and a JSON content type or the daemon's token — and a missing Origin is never trusted. A second hole found while testing the first: the loopback check matched by prefix, so a hostname like localhost.evil.example passed.
  • The database was world-readable. It holds your prompts and Claude's replies in the clear and was created 0644 while the config beside it was 0600. It and its WAL and SHM siblings are now 0600 on every open. SECURITY.md now says what is stored and how to delete it.
  • caprock up crashed with a stack trace when settings.json held {"hooks": []} or {"hooks": null} — the first command a new user runs.
  • Tokens from a model that is not in the pricing table displayed as $0.00, indistinguishable from free. The unpriced volume is now shown, with the models that caused it.
  • A fatal ingest failure was logged and swallowed: the daemon reported healthy and the dashboard said "no sessions yet" forever.
  • A new install opened on a screen of zeroes whose only coloured element was a cache warning about a cache that had never been used.
  • The explanation for a missing claude binary was unreachable, the Tasks screen showed "Error: 501 Not Implemented" instead of the server's actual message, and caprock statusline install exited silently when it had done nothing.
v0.15.12026-08-22

The task runner turns on from a button on the Tasks screen

  • no restart, no command to copy into a terminal. It confirms first, naming the queue directory and the repository, because this is the one feature that spawns Claude sessions with permission prompts skipped.
  • The Tasks screen explains itself in three steps instead of three paragraphs: you write a task with the commands that have to pass, Caprock runs one session per task in its own git worktree, Caprock runs the commands and only green is done. An empty board now says which button comes first.
v0.15.02026-08-22

Orchestration is now presented as what it is

  • an unattended task runner with a test gate. A task with an assignee shows its branch, and opens to the diff, the checks that passed, and the git command to take the work — none of which the product showed before, which is why the feature was hard to explain. The README documents it, a fresh hive seeds a README and an example task, and caprock status says which hive is active.
  • caprock task create, and POST /v1/orchestrator/stop to stop the orchestrator and every worker at once.
  • A task with no done_criteria reached done without being checked, contradicting the promise on the screen above it. Criteria are now required, and a task without them escalates instead of passing.
  • Three ways to spend money unattended with nothing to stop it: the orchestrator had no forced-continue limit, the wake loop had a throttle but no ceiling, and an over-budget task was parked as a file while its process kept running. All three now stop.
  • A task whose worktree was missing was verified against the main repository — which is clean, so it passed. Unverifiable is no longer treated as verified, and verification output is kept so a green result can be audited.
v0.14.22026-08-22

The Projects panel opens on the last seven days instead of thirty

  • A project worked on for two days out of thirty drew a sparkline that was almost entirely idle, which reads as no data rather than as a burst of work; a week is dense enough for the shape to mean something.
v0.14.12026-08-22

The per-directory breakdown now counts a turn toward the directory of…

  • The per-directory breakdown now counts a turn toward the directory of the most recent file it touched, and keeps counting there until work moves elsewhere — so the commands, tests and searches between two edits count toward the directory being worked on. The previous rule counted only turns whose every touch was in one place, which left 87.6% of a repository in a single row and answered nothing. Cost is still never split between directories, and the rows still sum to the repository total exactly. Turns whose work was outside the repository — notes, scratch files, test output — get their own row rather than being dropped.
v0.14.02026-08-22

Cost and tokens per directory inside a repository, taken from the fil…

  • Cost and tokens per directory inside a repository, taken from the files each turn touched rather than from where the session was launched — so a monorepo shows what each service costs without anyone having to run Claude from inside it. Every figure is measured: a turn counts toward a directory only when all of its work was there, and turns are never split between directories. Turns that ran commands, searched, or built are counted as repository-wide work, named as such and explained on hover, because dividing them up would be an estimate.
v0.13.02026-08-22

Each project row carries a sparkline of when its spend happened, buck…

  • Each project row carries a sparkline of when its spend happened, bucketed to follow the selected range — 30 days by day, today by hour — so the picture is always consistent with the figure beside it. It replaces the share bar, which restated the ranking the sorted numbers already gave.
  • Rows state both tokens and cost legibly. On a subscription the dollar figure is a proxy for consumption rather than a bill, and the second number used to sit in the faint chrome tone reserved for timestamps.
  • The breakdown bar inside an expanded repository scaled on the first row, assuming it was the largest. Rows are sorted by cost while the bar measures tokens, so another row could render past its track — reproduced at 900% width.
v0.12.22026-08-22

Projects are grouped by repository instead of by directory name

  • Work in ~/dev/caprock/ui counted as a project called "ui"; two unrelated paths ending in the same directory name merged into one row and summed together, so the figure was wrong rather than merely oddly labelled. Each repository expands to show where inside it the spend went, as paths — /, /app, /ui/src.
  • The login agent still throttled the dashboard. 0.12.1 replaced ProcessType=Background with Adaptive, which measured no better: the process still landed at scheduler priority 4 against a normal 20, and requests still took over a second where the same binary from a terminal took 185ms. Any ProcessType puts the job in a managed band, so the key is now omitted entirely. Re-run caprock service install to pick up the corrected agent.
v0.12.12026-08-22

The dashboard took over a second to answer every request when the dae…

  • The dashboard took over a second to answer every request when the daemon ran as a login service, which made the range switches on Cost and Projects appear to stick: a click highlighted the new range while the numbers stayed on the old one, because the request had not come back before the next refresh overwrote it. The launchd agent declared ProcessType=Background with LowPriorityIO — correct for a watcher, wrong for a process that also serves a UI, since macOS throttles the I/O of anything it has been told is batch work. The same binary answering the same query measured 1.2s under launchd against 185ms from a terminal; ProcessType=Adaptive closes the gap. Re-run caprock service install to pick up the corrected agent.
v0.12.02026-08-22

caprock service install|uninstall|status

  • registers the daemon with the OS's own login supervisor so it survives a reboot: a launchd agent on macOS, a systemd user unit on Linux, a Startup-folder script on Windows. All user-level, nothing written outside your home. The service runs with --no-hooks; hook and statusline registration stay interactive consent decisions.
  • Budget enforcement on the task board. budget_usd was validated, stored and rendered red past the limit while nothing compared cost against it; the reconciler now parks an over-budget task in needs you with the reason attached.
  • Finished tasks no longer accrue cost forever. The router opened a task's cost window on the session id and verification closed it on the agent id, so the window never closed — and an open window has no upper bound, so a completed task went on absorbing everything that session spent afterwards. A $0.42 task billed $9.42.
  • Verification can no longer strand a task. Both the success and failure paths guarded their transitions in a way that could silently no-op, leaving a task nobody could move and the next verify erroring outright.
  • The model mix is answered from an index. idx_events_cost_cover carries model but leads on kind, so a range query filtering only on ts read the table for every matching row — 146ms against 56ms over 30 days on a 190k-event database. Every /v1/stats/summary computes this, and the dashboard asks for one on an interval, so overlapping requests could queue faster than they drained and leave a panel waiting.
  • The pulse legend names what its colours compare against. The tiers are relative to each session's own median minute, but the labels read "cheap" and "expensive" as though absolute, so a short bar marked expensive looked like a contradiction rather than a quiet minute carrying a lot of context. The hover readout now prints the median beside the minute's own cost.
v0.11.22026-08-21

Answers looked like it had lost your history

  • The list loaded a fixed 500 notes and stopped, which on a busy machine is half a day — so it showed an answer from 22 hours ago followed immediately by one from 30 days ago, with nothing between. Nothing was lost; the middle had never been fetched. The list now pages backwards on demand, and reaches everything.
  • An answer did not feel connected to its session. Clicking through opened the session at the top of a timeline holding thousands of events, leaving you to find the passage you had just clicked. It now opens at the moment the answer was written, with those events marked.
v0.11.12026-08-21

"What did it ask?"

  • a session waiting on you now has a button showing the last thing Claude said, so you do not have to go back to the terminal to find out what it wants. It shows the last *complete* thought rather than the newest line: roughly a third of sessions end mid-sentence, and "Let me check that…" is not an answer.
v0.11.02026-08-21

A feedback button that files an issue without sending anything

  • Two clicks and a sentence: pick whether something is broken, missing or unclear, type what you saw, press the button. A prefilled GitHub issue opens in a new tab and you submit it yourself.
v0.10.12026-08-21

Over half the loop alerts were ordinary work

  • Replayed over 64,733 real tool calls, the detector fired 436 times — and 236 of those were a repeated Read of one file, which is what re-reading after an edit looks like. Tool calls cost nothing, so those alerts could not have been about the budget the feature exists to protect, and noise in an attention surface teaches people to stop reading it.
v0.10.02026-08-21

Live pulse

  • the shape of the work, one bar per minute. A track per session on Now, showing the last hour. A bar's height is how much happened in that minute and its colour is what it cost, so the shape of a track is the shape of the work: a task that ramped up and finished draws a bell, steady grinding draws a plateau, working in bursts draws a comb.
  • The pulse said "working" while Claude was waiting for you. It inferred a state from the bars instead of using the one the daemon already knew. The bars describe the past hour; health describes this moment, and they answer different questions.
  • ?newest=1 on the session events endpoint. Paging from the start returns the *oldest* events, so anything showing recent activity rendered an empty window on a busy session — hours of history, none of it recent, with nothing to say why.
v0.9.92026-08-21

"Active days" was undercounting by a third

  • The History screen read 21 active days on a database with 32 days of events, because it counted the days sessions were *started* rather than days with work in them. One session that ran twelve days contributed one. It also got four times faster along the way (0.37s to 1.63s and back down to 0.36s).
  • A partial settings body wiped the rest. PUT /v1/settings with an empty or short body answered 200 and reset everything: a stated plan reverted to "not stated", and the release-check opt-in switched itself off. The plan decides what every cost figure claims to be, and update checks are the only outbound call Caprock makes — neither may be toggled by omission. Settings are now a patch: a body changes what it names and nothing else.
  • Tasks were silently lost when created together. Ids were minted from the millisecond alone, so twelve tasks added at once produced four and eight "already exists" rejections. All twelve now succeed.
  • The tasks endpoint accepted tasks nobody could use — no title, a hundred-thousand-character title, a negative budget, or 1e308. Each is now refused with a reason.
  • An unknown API path answered 200 with a web page. The dashboard is served from /, so anything unmatched fell through to it — a caller that mistyped an endpoint got a success and HTML, then failed elsewhere parsing it as JSON. Unmatched /v1/ paths return 404 with a JSON body; the dashboard's own deep links still work.
  • ?days= out of range returned the wrong total. Asking /v1/stats/daily for everything clamped to the default of 30 days rather than the ceiling, so the answer was a month with nothing to indicate truncation. On a real database that disagreed with the summary endpoint by $1,603.
  • A query that stopped early passed for a complete one. Three loops ignored the error a partial scan reports, so a cancelled or failed read returned what it managed to fetch as though it were everything — twice followed by an update that changed every matching row.
  • "Files touched" is labelled honestly. It sums distinct files per session, so a file edited in three sessions counts three times; the tile says so instead of letting the number pass for a count of distinct files.
  • Test coverage of internal/ is 80.4%, with the packages where a bug reaches the user's own machine — the hook shim and the process manager — covered first. Two real defects surfaced there: a data race closing a PTY, and a clean exit reported as an error. See .ai/13-testing.md.
v0.9.82026-08-20

Every cost figure now says what it is

  • Shown to five readers, three took a large dollar number for a bill. The explanation existed on one screen and nowhere else — History said API-equivalent, which is internal jargon, and Cost and Session said nothing at all beside the number.
  • The dashboard has a visual hierarchy. Cost leads at the size the number deserves; reference figures step down. Previously the money sat fourth of six at the same size as a turn counter.
  • Percentages round down. A 99.5% cache hit shown as 100% is a perfect score the data does not support.
  • Cache hit is no longer permanently green. It sits near 99% forever, so an always-on colour pointed attention away from the money. It speaks up below 90%, where a drop means something broke.
  • An attention row for sessions with many turns and few files. Every other rule skips ended sessions, so this shape had no surface at all. It is worded as the measurement, not a verdict — reading, investigating and designing all look like this from the outside, and only the person who was there can tell them from a session that went nowhere.
  • Note search matches the prompt that produced an answer, not only the answer's own text — so searching your own words finds what Claude replied.
  • The live indicator stopped updating while nothing was happening. It computed the age of the last frame only when a frame arrived, and on an idle machine no frame arrives by definition — so it froze at live · now and kept claiming that for hours. A stale liveness indicator is worse than none, because it is the one people trust.
  • Cost subtitles no longer truncate on the Cost and Session screens.
v0.9.72026-08-20

The Claude desktop app's plan usage, on the status screen

  • If you also use the desktop app for work that never touches a repo, one line now says how much of your plan went there: 8% of the 5-hour window · 5% of the 7-day.
v0.9.62026-08-20

The dashboard is three to ten times faster on a large history

  • Measured on a real 184,000-event database:
  • Panels show placeholders while loading instead of announcing "No history yet" and then replacing it with real numbers. An empty state now means the answer really is empty.
v0.9.52026-08-20

The running version is in the header

  • "Which build am I on?" was a question you had to open the status page to answer. When release checks are on and a newer version exists, the chip shows v0.9.0 → v0.9.4 and links to the details. A build from source says "dev build" rather than showing a git describe string, and is never told it is out of date — it is not a published release, so there is nothing to upgrade to.
v0.9.42026-08-20

A session that looped twice showed two identical banners

  • same tool, same count, same cost — which reads as a rendering fault and makes the attention strip look untrustworthy. There is now one banner per session, showing the most recent alert.
  • The Tasks screen explained itself in terms of our internal build phases. "Phase 2 runs when..." means nothing to anyone outside the repo; it now says what to do.
  • The favicon and the in-app logo were different marks. Same colour, different shape, so a browser tab did not read as the product it belongs to. Both are now the triangle from the header, on the site as well.
v0.9.32026-08-20

The README and agent docs now describe what the product actually is

  • They still listed the screens as they stood several releases ago, and never mentioned two of the most useful things in it: per-repository cost on the landing screen, and Answers — the prose Claude wrote, searchable across every session. Nothing in the software changed; the descriptions caught up.
v0.9.22026-08-20

One bad timestamp could make every session invisible, permanently

  • A transcript line stamped in the year 9999 rolls past year 10000 in any positive UTC offset, and Go refuses to serialize that — which aborts the encoding of the whole list it appears in. The API then returned HTTP 200 with an empty body, so the dashboard showed no sessions at all while the database held them, with nothing in the logs. Because the event persists, restarting did not help. Fixed at all three layers: responses are serialized before the status code is sent so a failure is an honest error, impossible timestamps are rejected on the way in, and negative token counts are clamped.
  • A malformed live event could silently freeze the dashboard. A tool call is arbitrary JSON, and one with an unexpected shape threw inside the WebSocket handler — outside React, where no error boundary can catch it. That stopped every later update, so the page sat on stale numbers showing no error. Leaf values are now coerced and one failing listener can no longer starve the others.
  • A single missing daily cost blanked the whole 30-day chart rather than one bar, while the header still showed a total.
  • Figures that read as broken: an empty range rendered "NaNh NaNm" in the Avg session tile; ratios could print "$∞" or "Infinity%"; the plan progress bar overflowed its card when a session reported more steps done than planned; the loop banner could say "ran the same call undefined× in undefined min".
  • "Active sessions" was wildly inflated during a first run. A session is marked active on its first event and reaped later, so a backfill counted every historical session at once — a new user's first impression was a count in the dozens that then fell to one. It is now bounded to the last 30 minutes.
  • The Answers tab and search could crash on a note with no text, and the Now screen could crash on a session missing its stats or activity.
  • A range like 90d silently meant "today", so a longer range reported fewer sessions than a shorter one. Any <n>d range now works. The dashboard was never affected — it only offers the four presets.
  • The release gate could not see a stale dashboard. The built UI is committed to the repository so go install works without Node, and a check existed to catch it drifting from the source — but it was never wired into make check, and it used git diff, which does not notice new files. Since a rebuild emits a new filename, it reported a clean tree while an old bundle sat committed beside it. Caught one commit before tagging this release, which would otherwise have shipped a dashboard missing every UI fix listed above.
v0.9.12026-08-20

Numbers that lied

  • History's "files touched" ignored the selected range, showing the lifetime total under a "today" heading beside five stats that did move with it. "Avg session" reported 32h 59m — it measures first-event to last-event, so a session left open overnight counted its sleeping hours; it is now labelled "Avg session span · first to last event". The plan-value multiple divided all-time usage by a 30-day fee on the "all" range while the caption claimed 30 days. The bar readout announced "0 sessions" next to a $257 day. A session's token subtitle broke out input and output while the total is 99% cache reads.
  • Asking for more data returned less. SessionNotes, SearchNotes and EventsAfter fell back to a small default when asked for more than their ceiling — notes?limit=5001 returned 200 rows where limit=5000 returned 2372. All now clamp.
  • Stale plan limits presented as facts. Values relayed by the status line were stored unchecked, so a five-hour window could claim it resets in 2030. Implausible samples are rejected, and a window whose reset has passed is marked stale rather than rendered as a clock.
  • Active days were counted in UTC while the daily chart uses local time — "21 active days" beside a 31-bar chart.
  • Content you could not reach. The session timeline showed only the newest events with no way back, so a long session opened as a peephole onto its last few seconds; there is now a "load earlier events" control. Tool output — a failing test tail, a stack trace — was a 160-character stub whose only escape was raw JSON, and now renders as text. The Files tab capped at 100 while the stat above said 132, with nothing admitting the list was cut.
  • Controls that led nowhere. The spawn dialog was never rendered anywhere, though the Terminal tab told you to use it — so no session could ever be owned. It is now on Now. The settings screen offered nothing to set. The Tasks empty state explained itself in terms of our internal build phases.
  • Correctness and races. Starting the orchestrator and running verification used the HTTP request's lifetime, so a browser disconnect could leave a real claude process running with nothing recording that Caprock owned it, and could kill a five-minute verification mid-run. Configuration was read without the lock that guards it while the settings endpoint writes it. Two more places sliced UTF-8 by byte, the same fault fixed in the parser last release — activity phrases and loop samples could render as mojibake.
v0.9.02026-08-20

Answers

  • what Claude actually said. For a large share of sessions the deliverable is not the diff but the conclusion: "this is done, but I could not verify X; check with the team and then we finish it." Caprock always stored that paragraph and never showed it — the timeline rendered a 200-character slice on one line, so it survived only in terminal scrollback.
  • GET /v1/sessions/{id}/notes and GET /v1/notes?q=.
  • Assistant prose was clipped on bytes and corrupted. The transcript parser capped text at 2000 *bytes* and cut at an arbitrary offset, so non-English prose was clipped at roughly half the intended length and about a fifth of clipped rows ended in a corrupted character — landing hardest on closing summaries, the very thing worth keeping. Text is now clipped on character boundaries at a far higher limit, and a daemon started against an older database repairs the damaged rows once from the transcripts still on disk, rewriting only the text and leaving ids, costs and everything else untouched. On the author's machine that took 452 corrupted rows to 3.
  • Asking the events endpoint for more than its ceiling returned fewer. A limit above the maximum silently fell back to 500, so a caller requesting everything received the *start* of a session and could mistake an early fragment for its ending. It now clamps to the ceiling.
v0.8.12026-08-20

Optional update notice

  • When a newer release exists, Now leads with a line naming it and the exact command for how this copy was installed — brew upgrade caprock, scoop update caprock, or go install …/cmd/caprock@latest — one click to copy. When no package manager owns the binary it links to the release page instead.
  • GET /v1/update and POST /v1/update/check; update_checks added to /v1/settings.
  • Engineering rule 4 now states the exception honestly — "no outbound calls" became "no outbound calls except the release check the user explicitly turns on", in the rules, the product doc, and the README, rather than leaving a promise the code no longer keeps literally.
v0.8.02026-08-20

Live activity feed on Now

  • One column of what every session on the machine is doing, newest first, fed by the existing live WebSocket and seeded from recent history so it is never empty on open. A session list says what exists; the feed says what is happening. Only events worth reading become lines — successful tool results, assistant turns, cost ticks and mail are dropped, because a feed of raw event kinds is noise. Long absolute paths in shell commands collapse so the verb stays visible. Pause to read.
  • Plan value. What your measured usage would have cost at API list price, against what you actually pay. Caprock cannot detect your plan — Claude Code does not report it, and inferring one from usage would be an invented number — so you state it in a header chip that is one click from being changed. On a flat plan (Pro/Max/Team seat) you get a multiple; on metered billing (API key, Bedrock, Vertex, Enterprise at API rates) no multiple is shown at all, because that figure is approximately your real bill. It never says "you saved $X": without the plan you would not have run that much.
  • Attention strip. Reports a live loop (with the evidence and what that session has spent), a session that errored, and a session that has been waiting on you long enough to cost you time. There is no "all clear" state — it renders nothing when nothing is wrong. Being expensive is never on its own a reason to fire.
  • GET/PUT /v1/settings for the stated plan, validating rather than coercing so a typo cannot drive a wrong headline figure.
v0.7.02026-08-20

Per-project spend on Now

  • The landing screen opens with a Projects roll-up: one row per repository with its measured cost, tokens, session count, a bar showing its share of the largest project, and a green dot when a session is live in that repo. A session list could not answer "what does this repo cost and who is working in it" — this does. The range selector (today / 7d / 30d / all) defaults to 30d, because "today" is empty most mornings and an empty panel reads as broken rather than as an honest zero. Every figure is measured from captured events at API list price.
  • sessions in each projects entry of GET /v1/stats/summary — the count of distinct sessions that touched a project in the range.
  • Numbers now carry the cards. A stat's value sat at 17px against its own 10px label, close enough in weight that a card had to be read up close rather than scanned; values are larger throughout.
  • The orchestration graph left the top nav. With no orchestrator running it can only draw session ids around a hub — topology rather than work — so a permanent nav slot bought a screen that says nothing to a solo user. The route stays at #/graph and the Tasks board links to it while any task is assigned and unfinished.
  • The graph reads at a glance when it *is* meaningful: a headline of verified / in-flight / worker counts, each worker labelled with the task it is working on and a plain-language status, and larger nodes and gates.
  • Inline links no longer underline whole cards. A session card is an <a>, so a global hover rule underlined every label and number inside it; underlining is now opt-in for genuine inline links.
v0.6.02026-08-20

Light theme

  • A header toggle (sun/moon) flips the dashboard between the dark ops-room look and a light theme; the choice is persisted and, when unset, follows your OS preference. Every screen and the live terminal adapt.
  • go install works with a real UI. go install github.com/dspv/caprock/cmd/caprock@latest now embeds the built dashboard (the UI is committed and a CI check keeps it in sync), so a Go install is a full Caprock, not a placeholder page.
  • Plan-limit forecast uses the daemon clock (consistent + deterministic), and the status-line command quotes only the binary path — so a caprock installed under a path with spaces registers correctly.
v0.5.12026-08-20

Windows install via Scoop

  • scoop bucket add dspv https://github.com/dspv/scoop-bucket then scoop install caprock — no more hand-downloading a zip. The manifest is pushed to the bucket on each release, and the README, install guide, and site now show the Windows path alongside Homebrew.
  • Project map in the README linking the site, the Homebrew tap, and the Scoop bucket, so any repo in the project points to the rest.
v0.5.02026-08-20

Plan limits, set up for you

  • caprock up now offers to register the caprock statusline command (the 5h/7d plan-limit windows on the Cost screen) under the same consent contract as hooks — a TTY prompt, or --yes for scripts. New caprock statusline install / caprock statusline uninstall subcommands manage it explicitly. It backs up settings.json once and never touches a status line you set yourself. New users get plan limits without hand-editing any file.
  • CODE_OF_CONDUCT.md (Contributor Covenant 2.1), linked from CONTRIBUTING.
  • Honest first-run errors. When the daemon can't start (most often the port is already taken), caprock up now surfaces the real cause — e.g. "port 127.0.0.1 is already in use — try caprock status / caprock down, or --port <n>" — instead of a bare "did not report ready" timeout.
  • Readable MCP tool names. In History's Tool Usage, mcp__server__tool now renders as server·tool with the full name on hover, instead of truncating.
  • Release can't ship a red commit. The release workflow now runs a verify job (make check + Windows cross-build on the tagged commit) before goreleaser builds or publishes anything, and writes the Homebrew formula to Formula/ (not the repo root, which brew ignored — a stale formula could otherwise be served).
v0.4.12026-08-19

Homebrew install now includes the hook shim

  • The formula installed only caprock, not caprock-hook, so a brew install fell back to the caprock hook self-command. Both binaries ship now.
  • Hook status/uninstall recognize the self-hook form. When hooks were registered as …/caprock hook (no sibling shim), caprock status read 0/8 and hooks uninstall silently left them in place (causing duplicates on reinstall). Inspection now matches both the dedicated shim and the self-hook command, so status is honest and uninstall is clean.
v0.4.02026-08-19

Plan-limit windows

  • caprock statusline (register as Claude Code's statusLine.command) reads Claude Code's rate_limits and shows your 5-hour / 7-day window usage and reset time on the Cost screen, with an honest "at current pace" forecast shown only when your measured usage would reach the limit before the window resets. Pro/Max only; absent otherwise. The command also prints a compact one-line status (model · context% · cost · limits) and can never break or slow the session.
  • Orchestrator: workers now stop cleanly. A worker's fire-once mail lingered in its inbox after it acted, so the Stop-loop forced continuation forever and the worker was re-kicked into an endless inbox-poll. The router now archives a consumed message to the agent's processed/ dir once its task moves past the state that message was driving — both a picked-up assign and a verify-bounce the worker has since fixed. Live mail (questions, un-acted bounces) is kept.
  • Orchestrator: Start is idempotent and race-safe. Starting the orchestrator while a session is already live no longer spawns a duplicate (which leaked the first and raced a second router loop on the same hive); it re-kicks the live session so it picks up newly-queued tasks. A starting guard closes the check-then-spawn window so two concurrent starts can't both spawn.
  • Homebrew formula, not cask. A CLI binary ships as a formula (casks are for GUI apps), so install is now brew install dspv/tap/caprock (no --cask); the formula also works on Linux Homebrew.
v0.3.02026-08-19

On-disk hive (agents / tasks / mailboxes / append-only ledger), singl…

  • On-disk hive (agents / tasks / mailboxes / append-only ledger), single writer, atomic writes, dependency-free YAML.
  • Tasks board (kanban over tasks/*.md), New-task dialog, approvals.
  • Orchestrator agent — a real claude session with a hive-aware system prompt that spawns and coordinates workers via mailboxes; the router is a reconciler that spawns a worker per assigned task, runs verification, and wakes idle sessions with unread mail.
  • Stop-loop autonomy — a worker's Stop hook is answered to force it to keep going while its inbox is non-empty, with a hard guard (N=10) that escalates.
  • Verification before done — a task's done_criteria run in the worker's worktree; only green checks reach done; red bounces the failing output back (R=3 rounds, then escalate). Destructive commands never run unattended (they escalate to needs_you). Cost is attributed to the task.
  • New endpoints: POST /v1/orchestrator/start, POST /v1/tasks/{id}/verify; --hive / --repo flags.
v0.2.02026-08-19

Spawn real claude sessions from the UI into an optional git worktre…

  • Spawn real claude sessions from the UI into an optional git worktree, with a live xterm.js terminal (bidirectional) in Session Detail.
  • Owned-session controls: pause / resume / kill — only for sessions Caprock spawned; externally started sessions stay observe-only.
  • Opt-in auto-pause of a looping owned session.
  • History screen — lifetime stats: cost per project / day / model, tool distribution, model mix, top projects.
  • New endpoints: POST /v1/agents, /v1/agents/{id}/input|signal, WS /v1/agents/{id}/term, GET /v1/history.
v0.1.02026-08-19

Single static Go binary

  • a loopback daemon (127.0.0.1:4173) that serves the REST API, a /v1/live WebSocket, the hook receiver, and the embedded dashboard.
  • Hook plane — a tiny caprock-hook shim registered in ~/.claude/settings.json (non-destructive, backed up, cleanly removable) forwards the core Claude Code hook events; a broken or absent daemon never affects the user's session.
  • Transcript plane — tails ~/.claude/projects/, schema-versioned parser, tolerant of malformed lines and unknown fields; usage counted once per response.
  • Normalized event stream in SQLite (pure-Go, no CGO); cost from a versioned pricing table (Anthropic list prices, dated) and cache-savings math.
  • Loop detector — flags a session repeating the same tool with similar input (K=5 in T=3 min), with an alert banner.
  • Dashboard: Now (per-session narration, health, plan progress, live burn), Session Detail (event timeline, live git diff), Cost (burn, model mix, per-project, 30-day), History (lifetime stats, tool distribution).
  • CLI: caprock up | down | status | hooks install|uninstall|status | tasks.
  • Event retention (retention_days, default off) caps database growth.

← back to caprock.dev