Changelog
v1.27.0
LatestFindings can reach you now — a critical finding no longer waits for somebody to open the panel — and the learned home is portable, scriptable, and better at reporting on itself.
- Findings can reach you now. A critical finding discovered by the 3am scheduler used to be completely silent until somebody opened the panel — the store lives in the add-on’s
/data, which Home Assistant cannot see. The add-on now publishes a mirror to/config/.brain/findings_state.jsonon every change, and the integration builds three things on it: an Open findings sensor (open count as state, severity split and the texts as attributes, so an automation can put what is broken on a lock screen), abrain_findingevent per newly-filed finding, and an optional push notification — setfindings_notify_serviceto anynotify.*service andfindings_notify_min_severityto what is allowed to ring your phone (defaultserious). The store dedupes across every status and the settled ledger, so the same problem can never ring twice. brain findings— the Findings tab, scriptable.list,fix,done,wrong,ackandsnooze, all through the panel’s own API, so a CLI ending writes the same memory line, settled-ledger key and undo token the tab’s buttons do. The chat’s command palette picks the new subcommands up automatically.brain memory export/import— the learned home, portable. One JSON file carrying the memory document, the findings work list, the settled answers and the facts ledger (also downloadable from the Memory tab’s ⬇ Export button). Import is a migration, not a sync: ledgers merge with existing entries winning, the memory document replaces only an effectively-empty one unless you pass--replace-memory, and importing the same file twice changes nothing the second time.- The scheduler reports itself. Every category now carries
next_due, rendered on the card foot (“next in 4 h”), so “why did my cards stop updating” has an answer on the card instead of one log line printed once. The active auto-refresh gate (paused / budget reached / not signed in) rides along, and the countdown hides while a gate holds rather than promising a run that will not come. /api/healthcalls the roll. The liveness endpoint now reports which background daemons are actually running (worker pool, listeners, usage tracker, memory consolidator, study watcher, ttyd) plus when the last consolidation pass landed — informational only, so a dead sibling can never fail liveness and restart-loop the add-on.brain doctorreads it and compares against its own view.- Energy insights lean on Home Assistant’s long-term statistics. The analyst is told explicitly that
get_statisticsreaches back months (day/week/month buckets, surviving recorder purges) and is the tool for “compared to last week/month” — and the energy category asks for a period-over-period anchor instead of extrapolating a month from a few days. brAIn deliberately keeps no statistics store of its own: Home Assistant already has the sums.
- Picking a chat model updates the label under the composer immediately. The label is the only confirmation a pick landed, but the event that refreshes it does not arrive until the next message — a restarted
--resumeprocess says nothing until spoken to — so the picker looked like it did nothing. The response now carries the server-made label and the meta line updates on the spot. - The four Playwright layout measures now run in CI instead of by documented human habit — the two bar shapes, the 44px target floor, the never-truncated card title, the painted chat meta line and the on-screen tooltips are enforced on every PR.
- The facts ledger no longer ships a
remove_factfunction: nothing may be deleted from that ledger — deleting is how the analyst re-announces what you have already seen — and the one function that could sat there called by nothing.
v1.26.0
A round of fixes for things that failed quietly — sensors that went dark without a reason, options that did nothing, notifications that never arrived.
- Usage sensors that went unavailable during a 429 wall now say why — and a restart no longer makes the wall worse. The tracker’s backoff (1/2/4 hours) is deliberately longer than the two-hour window after which a reading is too old to trust, so during any streak of 429s the four usage sensors will go unavailable — that part is by design. What was broken: the reason was only written down on the poll after the reading went stale, which on a four-hour rung is hours later; in between, the
Usage trackerdiagnostic saidstaleand nothing else. A failed poll now records what failed and when it will ask again right beside the reading it deliberately left showing, so the moment the sensors blank, the diagnostic names the cause (http_429, with the note that this is the endpoint’s limit, not your account’s usage) andnext_attempt_atsays when it retries. And the backoff survives restarts: it lived only in memory, so restarting the add-on — the first thing anyone does when sensors go dark — polled immediately and retried straight back into the daily meter that caused the outage. - A restricted voice assistant could sidestep its service restrictions. Deny-listing
cover.open_coverdid not stophomeassistant.turn_on {"entity_id": "cover.garage_door"}— HA’s meta-services forward to the target’s own domain, and the deny check only matched the spelled name. Meta-calls are now checked against their targets and refuse anything touching a restricted domain (or targets the check cannot resolve, like whole areas).fire_event— which can trigger any automation, including one that does exactly what the deny-list forbids — is refused entirely on restricted channels. And the one-click deny options now includebrain.*, so a restricted agent can be kept away from the 65 registry-admin power tools (includingbrain.create_user, which can mint an admin login). - “Voice thinking limit” and “Voice tool access” did nothing in fast mode — which is the default. The worker pool never saw either option: it read its environment directly and the values were only written where the classic listeners re-source them. Voice always ran at 5 turns (config ships 8) whatever the slider said. Same class of bug as 1.25.2’s listener fix, in the one consumer that release didn’t cover.
- Voice conversations no longer lose their memory to the pre-warmed worker. A follow-up turn whose worker had been reaped could be handed the fresh spare process — which knows nothing — and the stored session id was then overwritten, severing the conversation’s context for good. The spare now only serves conversations that genuinely start from nothing.
- Deleting a chat conversation could destroy it while the toast still offered Undo. The trash judged expiry by file mtime, which is a conversation’s last-activity time — routinely older than the 30-minute grace period — so a just-deleted conversation could arrive “already expired” and be unlinked by the very next delete. Trash entries are now stamped at deletion time, and restore puts the original timestamp back so the row keeps its place in the rail.
brain_learnedlogbook events now actually fire. The watcher that turns newly-learned facts into logbook entries (documented since it shipped) was never started.ha contextalways failed. The dispatcher ran delegated scripts withbash, which ignores theirwith-contenv bashioshebang — so the one script that logs through bashio died with “command not found” on its first line, every time. Both dispatchers now exec the script itself.- Automation task notifications never arrived. The listener posted with an
entity_idkey the notify schema rejects, so a task withnotify: trueproduced a silent 400. The requestednotify_entitynow picks the notify service, as documented. - A failed study session no longer silently doubles its own cost. The study runner misread every failure’s exit code as success-shaped, so a session stopped by its own timeout triggered a full second session, and every failure reported the same unhelpful message with stderr thrown away. Failures now report the real reason — timeouts by name, anything else with the CLI’s own last line.
- Smaller fixes: two panel writers can no longer silently lose a findings update to each other; the run-sources prune no longer hands the ledger to root; the MCP watchdog no longer rewrites arbitrary JSON files that merely mention
/api/mcp; notebook edits are snapshotted before Claude changes them sobrain undocovers them; a malformed terminal handoff no longer kills the terminal session at open; a malformed JSON body to the findings endpoints gets ignored instead of a 500; a chat stream that falls behind reconnects to a fresh snapshot instead of idling forever;memory_max_kb’s fallback agrees with the shipped default.
v1.25.2
- “Automation thinking limit” and “Voice thinking limit” did nothing. Whatever you set them to, automation ran at 10 turns and voice at 5 — the fallbacks. The settings were saved correctly and written where the listeners could find them; the listeners just read the number a moment before that file was loaded, so they kept the fallback and never looked again. This is why an agentic task — anything that searches the web, reads several entities, then writes something — would stop early and answer with whatever it had: ten turns is tight for that work, and
automation_max_turnsnow genuinely ships at 30. Study sessions run from the terminal had the same fault, sostudy_max_turnsandstudy_timeout_minuteswere ignored on that path. - The fallbacks disagreed with the documented defaults. If the add-on ever can’t read its saved environment, the listeners now fall back to the same numbers
config.yamlships (30 and 8) — one answer per setting instead of a second one that only appears when nothing is watching.
v1.25.1
- The model picker looked like it did nothing. Choosing a model always did change the model that answered you — what it didn’t change was the name printed under the composer, the only place you can see which model is running. The panel worked that name out with a pattern that dropped the minor version, so Haiku 4.5 read as “Claude Haiku 4” and Sonnet 4.6 as “Claude Sonnet 4”: any two models of one family printed the same thing, and swapping between them left the screen unchanged. (An older id like
claude-3-5-sonnetfared worse — “Claude Sonnet 2”, from the date stamp.) Names now come from the add-on itself, off the model Claude Code reports it resolved. - The context percentage kept the old model’s window after a switch. The conversation carries across a model change, so the token count is still right — but the window belongs to the model. Switching to Haiku left the pill dividing by Sonnet’s 1M and reporting 4% of a window it was 21% into. It’s recalculated the moment the new session starts.
- The picker’s “Default” row named the wrong model. It read the global model once, when the Terminal tab opened — change it in ⚙ from the same tab and the row went on naming the model you had just replaced.
- The model popover jumped to the corner of the screen if the window resized while it was open from ⋯ → Model. It closes instead.
v1.25.0
When Claude has a question, the chat shows the question — and the self-test learned to check the parts that have been failing quietly.
- Claude’s mid-task questions render as questions. Claude Code sometimes asks multiple-choice questions mid-task (
AskUserQuestion— which zone did you mean, which approach do you prefer). Those arrived as a generic “may I use AskUserQuestion?” permission card, and allowing it sent the tool an empty answer sheet that broke the turn. The chat now renders the questions themselves — tap an option, pick several where allowed, or type your own — and the answers ride back on the CLI’s own wire. Don’t answer tells Claude to use its best judgement, and renders amber, not as a crash. - Unknown control requests are answered, never dropped. A
control_requestis a question the CLI is waiting on; one from a feature this panel has never heard of used to disappear into silence, which from the chat looked like Claude thinking forever. Anything unimplemented now gets an error response back so the CLI can fail that one feature and carry on with the turn. - Delete several conversations in one pass. A checklist button above the chats list turns on selection mode: checkboxes per row, Select all, and one Delete with a single Undo that puts the whole batch back. The open conversation is skipped and reported rather than failing the batch, and a batch is capped at what the trash can restore.
- The self-test covers the parts that failed quietly.
brain doctornow also checks: the panel and chat API are answering, the terminal’s password gate is actually on (an unauthenticated 200 on :7681 is a FAIL, not a curiosity), the Claude CLI itself runs, the three background daemons are alive, the run-sources ledger is claude-owned (root-owned means background runs get filed as yours, silently), a crashed consolidation pass still holding the lock, disk space, and how stale the usage reading is.
- The generated
/config/CLAUDE.mdstopped promising git backups. It still told Claude edits were “auto-backed up via git” — a feature removed releases ago — so Claude repeated it to people as if it were true. It now teaches what actually exists: edits are snapshotted before Claude makes them, andbrain undoreviews and reverts them. - The self-test’s “tracker polls every ~2 min” hint predated the 30-minute poll and sent people waiting on a cadence that no longer exists.
v1.24.0
The chat asks for permission instead of failing silently — the main reason chat answers came up short of the terminal’s.
- The chat asks for permission. Headless Claude Code cannot put a prompt on a TTY, so a tool call outside the pre-approved set never ran and the answer was written around the gap. The same question now arrives as a card in the conversation (what tool, aimed at what, Allow once / Don’t allow), carried over the CLI’s own control channel — the wire the Agent SDK rides. Everything already allowed in
settings.local.jsonstill runs without asking; an unanswered card declines itself after ten minutes and says so; a CLI too old for the channel just gets the old behavior back. - Thinking streams live. The chat only rendered reasoning when the message closed, so a long think was minutes of dots followed by the reasoning delivered after its conclusion. Thinking deltas now stream in as they happen, exactly like answer text.
- A status line for the whole turn. The three dots vanished on the first token, leaving a tool-heavy minute looking hung. The line now stays for the turn and says what Claude is doing — thinking, writing, which tool is running, waiting for your approval — with elapsed seconds, like the native CLI’s bottom line.
- The docs say why chat sessions don’t show in the Claude app. “Connected” sessions ride Remote Control, which only supports interactive sessions — a Claude Code limitation, not a setting. The session popover and the guide say so, and point at the face switch, which moves the same conversation into a terminal session that can register.
- The chat’s model picker could apply nothing. “Is a restart needed” was answered by comparing a pick against the panel’s intent, which is refreshed from settings on every request — so after a ⚙ edit the picker said “already that model” about a live session still running the old one, and a long-lived session never respawns on its own. Every model question is now asked of the model the process was actually spawned with; a change made anywhere lands on the next message at the latest, and
--modelstays on the argv across--resume, because a resumed session otherwise keeps the model it remembers. - Hitting the session turn limit no longer dead-ends the conversation. The cap guards against runaway loops but spans the process, and the old answer — “start a new chat to carry on” — charged the person’s conversation for it. The next message now restarts the CLI with
--resume: the counter resets, the conversation carries on. - A session error with a stderr tail could crash the refusal. The message went into an HTTP reason line, which cannot carry newlines, so “the session died” surfaced as a 500 about carriage returns.
v1.23.0
The chat picks its own model, and conversations can be deleted — without either touching what the rest of the add-on costs or the CLI’s own files.
- The chat picks its own model. Press the model name under the composer — or ⋯ → Model — and choose from the same list ⚙ Settings offers. The choice is the chat’s own (
chat_model): it never touches the global model option, so insight runs and the listeners keep costing what the Configuration tab says. Applying it restarts the CLI with--resume, so the conversation carries straight across. - Conversations can be deleted. Every row in the Chats rail and the Conversations dialog grows a ✕, and the toast grows an Undo — the file is moved into a short-lived trash rather than unlinked, so a mis-tap puts back exactly what was taken. The open conversation is refused (“start a new chat first”), and nothing ever edits the CLI’s own files: deleting a whole conversation is the one mutation, and it is a move.
- Opening an old conversation could leave a chat that errored on every message. Claude Code prunes old sessions from its store; resuming one it no longer holds spawned a CLI that died silently, and the kept resume id respawned the same failure forever. The spawn is watched until the CLI says its first word now: a refused resume drops the id, says so in the transcript (the replayed history stays on screen), and opens a fresh session — the panel tells you the context is gone rather than letting the next answer reveal it. A fresh session that dies on startup reports the CLI’s own stderr instead of a shrug.
- The model and context line under the chat was clipped — at every width, and worst on a wide screen. A leftover negative bottom margin shortened the page wrapper, whose
overflow: hiddenswallowed the bottom ~20px of the view: exactly where “Claude Sonnet 5 · 132k / 1000k context” lives. Fixed, andtests/manual/measure-chatmeta.mjsnow measures the line withelementFromPoint— painted, not merely positioned — so it cannot quietly go under again.
v1.22.3
- The usage sensors worked all night and died by mid-morning. 1.22.2 cut the requests per poll from six to one and still the sensors hit a wall of 429s every day — recovering at roughly the same hour each night. That nightly recovery is the tell: a burst limit clears in minutes; a limit that comes back at a fixed hour is a daily allowance. The arithmetic matches exactly — nine working hours at a two-minute poll is about 270 requests, the reported window almost to the minute. Against a per-day cap the only lever is how many requests a day costs, so the poll is now every 30 minutes — 48 requests a day instead of 288, or the ~4,300 the six-per-poll version could reach. What that costs is resolution nobody can see: the five-hour window moves about 1% every three minutes at a hard sprint, so a half-hourly reading is never more than a percent or two behind — and a sensor slightly behind all day beats one that is exact until 10am and unavailable after it. The 429 backoff moved from 15/30/60 minutes to 1/2/4 hours to match: every step must exceed the poll interval, a rule with a test behind it, because lengthening the poll alone would have meant a failing tracker asking more often than a working one.
v1.22.2
- The usage sensors reported a rate limit brAIn had caused itself. They went unavailable with
http_429, which reads as “you have used up your quota” and was nothing of the sort — the usage endpoint was refusing to answer, with plenty of quota to spare. brAIn was asking it six times per poll: the tracker offers a credential per path, and the paths are the same file —CLAUDE_CONFIG_DIRmakes two of them identical strings and symlinks join the other two. One sign-in, four identical requests with no pause between them, then two more from the panel andha loginstores usually holding the same token — every two minutes, all day. That endpoint is undocumented and limited far harder than anything else brAIn touches, and a token hammered on it keeps answering 429 long after the window that supposedly caused it. Claude Code itself only ever calls it when you open its/usagescreen, never on a timer. A credential is now offered once however many paths lead to it; a 429 is treated as the one failure retrying makes worse — waits of 15, then 30, then 60 minutes, andRetry-Aftercan only ever make brAIn wait longer, because the endpoint sendsRetry-After: 0while still refusing. A rate limit never blanks numbers that are still good — it says nothing about your sign-in, so the last reading ages out normally — and the Usage tracker sensor carries adetailexplaining thathttp_429is the endpoint’s limit and not your account’s.
v1.22.1
- Two things saving at once could lose one of them. Every store in the panel wrote its file the same way — write
findings.tmp, then rename it overfindings.json. Safe against a reader, exactly wrong against a second writer: the scratch name is derived from the target, so every writer picks the same one, the first rename moves it, and the second’s bytes went into a file the winner had already renamed away — silently lost. It was reachable: pressing Fix it writes the findings store from the web request while an insight run writes the same store from the background worker, and the same pattern was in all seventeen places the panel saves a file. Saving now uses a scratch name nobody else can pick. Two details a naive fix would have broken came along: the file keeps its permissions (several are written as root and read by theclaudeuser) and its owner — the old rename silently took/data/run-sources.jsonlaway from theclaudeuser on every prune. Files are also flushed to disk before the rename, so a power cut can no longer leave a file that exists and is empty.
v1.22.0
Every card now says what it cost, and fetches only what it needs. The most expensive thing brAIn does is now readable on screen — and much smaller.
- Every card says what it cost. Generating an insight is the most expensive thing brAIn does — typically 25k–45k tokens a card — and until now the only evidence was the usage pill moving, with nothing saying which card moved it. The figure is readable in four places, all reporting the same number: the spinner line while it runs (
500 entities · ~33k tokens sent), the card’s footer after (41.2k tokensbeside the stopwatch, input/output split behind a hover — seconds and tokens are different readings), the usage pill’s popover (What brAIn spent, this session, per card, biggest first), and one line per run in the add-on log. The itemization is scoped and says so: these are brAIn’s own runs — when the percentage above it is your account’s live figure it covers terminal, chat and voice too, and a breakdown read as exhaustive is how you conclude a terminal session is free. - A card fetches what it needs instead of being handed the house. ⚙ Settings → How a card gets its data, and Search is the new default. Claude gets a map of your home — how many entities of each kind, which areas, a few anchors — plus read-only Home Assistant tools, and looks up what the card actually needs: a question about the hallway costs the hallway. Measured on a real run: a 1,449-character prompt where the old path sent about 100,000. It is also the only mode that can afford history on a question you type — the one-shot path never could, so typed questions were always answered from a single instant. Snapshot is the old behaviour, kept as a setting and the automatic fallback so a card always appears; the fallback is logged, because a run that keeps taking it is worth knowing about.
- Insight runs can only read. The analyst’s tools are an explicit allow-list of the reading half of the HA MCP server, and every acting tool is explicitly denied as well — belt and braces, because
--allowedToolsgoverns what runs without a prompt and a headless run cannot be prompted, so an un-listed tool merely fails rather than being forbidden. Not the same guarantee with a real house on the other side.tests/test_security.pychecks the deny list against the MCP server’s own tool list, so adding an acting tool and forgetting this fails in CI rather than in somebody’s home. The one path that changes anything is still Fix it, which a person presses.
- A card’s data snapshot got 29% smaller, and lost nothing. A question sends every entity in the home, so a character on one row is 500 characters on the prompt — and three fields were paying for something nobody read.
lcis now minutes since it last changed, not a 19-character ISO timestamp (staleness is the only thing it was ever read for, and “6 minutes ago” and “an hour ago” are different facts that cost the same).nis dropped when it is just the entity_id prettified — HA’s default name, a second copy of a string already on the row; a renamed entity keeps it. An unavailable entity keeps only its id, state and area. Measured on a 500-entity home: 72,943 → 52,086 characters, roughly 5k fewer input tokens on every card and every question. - The docs now say what a card costs, and which kind is expensive: a category card sends its slice of the home, a question typed into the ask bar sends every entity plus device context — which is why a few asked questions can cost what a dozen scheduled refreshes do. Settings & cost lists it as the first lever, above the scheduling ones.
v1.21.0
Undo, in the toast — because “I fixed it” and “Wrong” sit next to each other meaning opposite things, and a mis-tap had nothing to put itself back with.
- Undo, in the toast. Every press that takes something off the Findings list — both endings, Got it, Dismiss, and either answer to a guess — now leaves an Undo button in the toast for a few seconds. It puts back all of it: the card, the suppression that would have stopped brAIn raising it again, and the line it queued for your memory document. Deliberately short-lived: this is “I pressed the wrong one”, not a history — once a consolidation has run, the fact is in the document and editing the document is the honest answer, and Undo says so rather than pretending. Fix it has no Undo, because it has already sent Claude at your actual house and taking the card back would be a lie about what was undone. Remind me later has none either: it took nothing away, and it already has Bring it back now.
- “I fixed it” takes a note too. The same box Wrong offers, for a different reason — nothing is being corrected, so what you type is simply more of the fact: “Replaced the CR2032 — it’s a 3-monthly job on that one.” “I fixed it” leaves brAIn knowing a problem is over; that sentence leaves it knowing your house. Optional, like the other one.
- “9 things waiting” over four cards. The Memory tab’s filing queue counted one thing and listed another: the count read the memory inbox — every fact any writer has queued — while the list read the facts ledger, which only holds what insight runs discovered. So corrections, confirmed guesses, facts you typed in yourself, voice, study sessions and anything another add-on left in
/sharewere all counted and never shown. Neither number was wrong; they were answers to different questions. The list is the inbox now — every row is a line the next pass will actually read, labelled with where it came from — and the count is the length of it, from the same read. ✕ on a row drops it from the queue and asks the consolidator for nothing, because a queued fact has never reached the document. - Tooltips ran off the left of the screen. They were anchored to each control’s right edge and up to 240px wide, so anything in the first ~236px lost its text off the side — four of the six buttons under a finding on a phone, and still two on a desktop. They are measured and clamped to the window now, and open upward when there is no room below.
- The toast was squeezed into the right-hand half of the screen. It was positioned from the centre, which on a phone left it about 195px to lay out in — text wrapped to four lines with the whole width to spare. Nobody noticed until the Undo button had to sit beside it.
v1.20.0
One list of things waiting on you. brAIn asks two kinds of question — is this broken? and have I got this right? — and the second was being asked in three places while the badge counted none of them.
- Guesses now live on the Findings list, and the badge counts everything waiting on a decision. They used to be answerable inline under every insight card and in the Memory tab’s “Waiting on you” — so answering one left the same guess sitting elsewhere looking unanswered, and a Memory tab badged with work you didn’t have to do is how you learn to stop reading the badge next to it, which counts work you do. Guesses keep their ✓ Yes / ✕ No, and the Memory tab is what it should have been all along: a queue that files itself and a document to read, with no badge, because nothing on it is waiting for you.
- “Wrong” replaces “Ignore” on a finding, and it asks why. The old button said what happened to the row; what people actually mean is usually you’ve misread my house — the sensor isn’t stuck, it’s a contact on a cupboard nobody opens — and it had nowhere to say so, so the next run made the same mistake in different words. ✕ Wrong opens a box for one sentence, which goes into your memory document at the next consolidation and into what every future analysis is told about this home. It is not treated as an instruction — brAIn is handed what you said and works out what follows from it, usually a standing fact, sometimes nothing at all, so you don’t have to phrase a correction carefully for it to be useful. The box is optional: press Send empty and it behaves exactly as Ignore did. The same box is offered when you turn down a guess and from the chat’s action strip — telling Claude there reaches that one conversation, and the box reaches every future one.
v1.19.7
- The add-on installs from a prebuilt image instead of building on your machine.
config.yamlnow carries animage:key pointing atghcr.io/bruhautomation/{arch}-brain, published by CI for both architectures on every change. Installs and updates become a download rather than a container build — on a Raspberry Pi that’s minutes of SD-card writes replaced by a pull, and one less thing that can fail halfway through on a flaky network. Everyone gets the identical image, and no action is needed: the next update simply arrives the fast way.
v1.19.6
- The srcdoc height message had no origin check — and could not have had a useful one. Every insight card renders in a sandboxed
srcdociframe, and those all report their origin as the string"null", so an origin comparison cannot tell one card’s frame from another’s. The handler checks window identity against the frame it is addressed to instead — the rule the terminal frame’s keyboard message already followed. - An insight id containing
</script>would have escaped the snippet it was embedded in. The height-reporting script interpolates the card id throughJSON.stringify, which does not escape<. It does now. - The terminal handoff file was world-writable.
/data/terminal-handoff.jsonwas chmod0o666so theclaudeuser could read and remove it; it is chowned to that user and0o600now. Removing a file was always governed by the directory, which that user already owns — the write bit bought nothing and granted it to everyone. - Path containment is now proved where the path is built. Insight ids, history stamps and card names were already behind anchored allowlists that cannot express a separator — but the guarantee lived in a regex somewhere up the call stack.
_under()joins under a base directory and refuses anything that did not stay there._unmirror_cardhad no allowlist at all, and its directory is under/config/www, which Home Assistant serves.
- The bundle collector and the memory writer no longer put exception text in a response. An
OSError’s text is an errno and a path; both now log the detail and answer with the sentence the user can act on. - A streaming conversation that ended without a result event had an unreachable error branch — the fallback to file IPC was always the right answer, and the
RuntimeErrorbeside it could never run.
- Every deliberately silent exception handler now says what is lost when the exception is ignored — 60-odd of them, from a failed cache write to a client that disconnected mid-stream. They were all intentional; none of them said so.
v1.19.5
dangerously_skip_permissions: falsedid nothing in the session picker.brain-menu.shread the flag as${BRAIN_CLAUDE_PERMS_FLAG:-$PERMS_FLAG}with the variable initialised to--dangerously-skip-permissions— and that failed open twice over. A missing/data/.brain_envmeant “skip every prompt”, and:-cannot tell empty from unset whilerun.shwrites exactlyexport BRAIN_CLAUDE_PERMS_FLAG=""when the option is OFF. So the dangerous fallback fired on the normal path: every session started from the picker ran with permission prompts disabled regardless of the option. Reachable wheneverauto_launch_claudeis off, which is what puts the picker in front of you; the default path was never affected. The flag now starts empty and is only ever set to whatrun.shsays. A security default may only fail closed.
- The chat says when a tool call was refused rather than broken. Headless
-pcannot prompt, so a call outside the permission set never runs — but it came back as a plain red Error, indistinguishable from a crash. It now renders amber, labelled “Not permitted”, saying nothing is broken and that the terminal can approve a call the chat cannot. The match is on the CLI’s own wording and deliberately narrow: a barePermission deniedis not treated as a refusal, because that is an ordinary EACCES from a permittedBashcall, and calling it a policy decision would send people to change a setting that was never in the way.
v1.19.4
“The chat works but the terminal asks me to log in, and the usage sensors are dead.” One fault with three faces — and 1.19.3 fixed the wrong half of it.
- An expired CLI token won everywhere it mattered. Two functions resolve the same credential in opposite order: the chat reads the panel’s store first, the terminal reads Claude Code’s own
.credentials.jsonfirst — and decided it was usable with a prefix test,startswith("sk-ant-"), which says a token is shaped like a credential, not that it is one. So the terminal deferred to a dead token, exported nothing, and prompted for a login while a working panel credential sat unread; the usage tracker walked the same order into a 401; only the chat kept working. And it could not clear on its own:run.shrestores.credentials.jsonfrom a backup whenever it goes missing, so deleting the dead token brought it back on the next boot. Fixed by checking the expiry rather than the prefix in all four places that ask —brain-auth-env.sh, the usage tracker,engine’s status, and the backup restore, which now discards an expired backup instead of resurrecting it. A live CLI login still wins, because aclaude /logindone in the terminal is the most recent thing the person did there. - A refused credential no longer speaks for the ones behind it. The tracker tries each store in turn and falls through on a 401, so a stale token in one place cannot mask a working sign-in in another. Anything that isn’t a 401 stops the search, because that is about the request rather than the credential.
ha selftestnames this failure directly — it reports the CLI credential’s expiry and, when it has passed, gives the two files to remove, including the backup, without which the next restart puts it straight back.
v1.19.3
- Every usage-limit sensor was unavailable if you signed in through the panel. The tracker looked for a credential in exactly one place — Claude Code’s own
.credentials.json— while brAIn keeps one in three: a panel login under/data/secrets, and the oneha loginshares under/config/.brain/secrets. So the terminal, the voice listener and the fixer all ran happily off a login the tracker insisted did not exist, and four sensors said “not authenticated” about the one thing that wasn’t wrong. It now resolves a credential the way everything else in the add-on does, and runs as root, because two of those three stores are root-owned0700directories — running it as theclaudeuser is precisely what limited it to the one store it could read. - A sign-in that cannot work now says which one it is. An Anthropic API key bills per token and has no subscription window, so there is no utilization to report and never will be; that used to arrive as
no_oauth_token, which sends people to redo a login that worked. - Four sensors going unavailable with no stated reason. A new
Usage trackerdiagnostic sensor never goes unavailable, because its whole job is to be readable at the moment the others are not:ok, or what stopped it —no_oauth_token,api_key_has_no_usage_limits,http_401,network_error,stale,not_running. - Stale utilization was reported as live. Readings now expire after two hours — the window the panel already applied to the same file — and a single failed poll no longer blanks four working sensors: a fresh reading is left alone to age out instead of being overwritten with an error.
- The chat’s context pill claimed more tokens than the window it measured against. Two mistakes, compounding. The count came from the
resultenvelope, whoseusageis every model call in the turn added up — and each call re-sends the conversation, so a ten-tool turn reported roughly ten conversations’ worth. It now reads the per-callusageon theassistantevent. And the denominator was a table where every Opus and Sonnet was 200K, which stopped being true at 4.6 when both went to 1M; the window is now read from the model’s version, and a model whose version can’t be read reports a token count and no percentage rather than a percentage of a guess.
v1.19.2
- The integration page read “brain brAIn” instead of showing a logo. The artwork existed and had been staged for over a year for a submission to home-assistant/brands that could not be made: since Home Assistant 2026.3.0 that repository closes any pull request adding a new custom integration automatically, before a human sees it. What replaced it is a
brand/folder shipped beside the integration’s own manifest, which Home Assistant serves itself and prefers over the CDN — the icon and wide lockup now appear with nothing to submit and nobody to wait for. The logos are 341×256 and 682×512 rather than the store’s 512×384, because the brand spec caps a logo’s shortest side at 256 — the staged assets would have failed the very validator that submission was aiming at.
v1.19.1
- “Back up Home Assistant first” now says so where it matters. brAIn edits your real configuration — automations, dashboards, helpers, entities. It snapshots every file before it changes it, and
brain undoputs them back; none of that is a backup you can restore from, and the gap between “careful” and “recoverable” is the one worth naming out loud. The notice sits on the first-run screen, on screen before brAIn is allowed to change anything, and in the README and DOCS for anyone deciding whether to install. It is styled as a caution, not an error — red in this panel means “something went wrong just now”, and a permanent notice wearing it teaches people to read past red.
v1.19.0
The terminal port was a shell anyone on your network could open. This release closes it, sandboxes the container, and keeps your Claude credential out of your backups. brAIn now rates 6/6 in the add-on store.
- The published ttyd port is gone.
ports: 7681/tcp: 7681published the terminal to the LAN on every install, with--writableand no--credential— anyone who could reach your Home Assistant box could openhttp://homeassistant.local:7681and get a root shell with/configread-write and a Claude Code signed in to your Anthropic account, with no Home Assistant login involved at any point. This is the exposure documented in GHSA-gh5m-4m97-c95h. Ingress never needed the port — the panel reverse-proxies/terminal/to ttyd over loopback. If you were using the direct port for a wall tablet, assign it again under the add-on’s Network settings. - ttyd requires a password now, published or not.
run.shgenerates one into/data/terminal-credentialand the panel presents it upstream on every proxied request, so nothing changes for anyone coming in through Home Assistant. The proxy drops anyAuthorizationheader the browser sends before adding its own, so a cached credential for the ingress origin cannot be replayed at ttyd.webui:is gone with it — it pointed at the raw port, where nothing now listens. - An AppArmor profile. brAIn runs an agent that edits files and runs commands, so a profile enumerating permitted binaries would break the first time you asked for something new. This one constrains what brAIn can do to the host instead: no mounting, no kernel modules, no raw sockets, no writes to kernel tunables, no Docker socket, no ptracing out of the profile.
- The Claude credential no longer rides into your backups. Home Assistant backups are unencrypted unless you opt in — then get copied to cloud storage, NAS shares and support tickets.
backup_excludenow keeps the OAuth credential, the terminal password and the chat scrollback out of them. Restoring costs you one sign-in.
- Every option has a name and an explanation in the UI.
config.yamldocumented all 35 of them in comments nobody installing an add-on reads, while the configuration page showedassist_max_turnswith a blank description.translations/en.yamlmoves that writing to where it is read. - A watchdog. The panel is the ingress target and the foreground process, so a hung panel was a dead add-on that still read as “started”. The Supervisor now polls
/api/healthand restarts it. stage,bootand a minimumhomeassistantversion, declared rather than inherited from defaults.
v1.18.3
- The sidebar icon is a brain. It was
mdi:home-analytics— a house with a chart in it, which describes the Insights tab and none of the other four. Home Assistant only takes an MDI name in that slot, so the sidebar can never carry the mark itself; what it can do is say what the add-on is, and brAIn is the mind you gave the house.mdi:brainit is.
v1.18.2
- The icon says which add-on it is.
icon.png, the panel favicon and every square brAIn shipped were the gable on its own — the family mark. It says BRUH and nothing about which add-on you are looking at, so brAIn and BRUH Minecraft arrived in the same sidebar wearing the same roof. Every square now comes from the full lockup: the parent’sBRligature, the gable that doubles as theA, andAINin smooth caps. Gable-only art is gone from the repo, and a test keeps it gone.
v1.18.1
- “brAIn is filing memory now” that never stopped saying it. The Memory tab could sit forever announcing a pass long after it had finished, and File into memory now did nothing visible. The lock:
with_lockopened its file descriptor and never closed it, so the lock was held for the life of the process rather than the length of the pass.--oncegot away with that for years because exiting releases the lock for free — but the daemon calls it in a loop and outlives every pass, so its first consolidation held the lock until its next one, a day later. Consolidation itself kept working, which is why it hid; what broke was the reporting, because the lock is also the panel’s only honest answer to “is a pass running right now” — so the tab announced a finished merge forever and the button answered every press with{"started": false, "running": true}. The lock is now released when the pass ends, in the contention path too.
- A pass says how long it has been going. “This takes a few minutes” answered a question nobody was asking — what you want while watching is the difference between slow and stuck. The banner now counts up: Filing these into the memory document now… (2m 10s), for the daemon’s passes as well as the button’s. Elapsed time is measured on the add-on’s clock and sent as seconds, so a phone whose clock is minutes off still reads right.
v1.18.0
A full memory document stopped filing anything, forever. Once memory.md reached its size cap, every consolidation refused itself and nothing was ever filed again — a loop that got further from succeeding the longer it ran.
- The size guard changed nothing about the next attempt. The pass asks Claude for the whole updated document and rejects an answer over the cap — but the daemon then ran the identical prompt every five minutes, got the identical over-size answer, and refused it again, while the queue grew behind it asking each attempt to fit more into a document already full. An overshoot is now measured and fed back — “your last answer was N bytes over, drop the oldest and lowest-value facts until it fits” — and only a second overshoot fails the pass, naming
memory_max_kbinstead of a byte count nobody can act on. - Background passes can label themselves again. The run-source ledger is written by the panel as root and by the consolidator and study watcher as the
claudeuser. Root created it first, so every daemon claim failed withPermission deniedand ran unlabelled — defeating the 1.17.0 change that keeps machine conversations out of your Chats rail. It is created claude-owned up front. The failure also printed a bash error on every pass despite the library being documented as silent:>> file 2>/dev/nullsilences the command, not the shell’s own “cannot open” message. - “File into memory now” logs where you were told to look. A button-started pass had its output captured and discarded unless the script exited non-zero — while the failure it reported sent you to read those lines in the add-on log. They now stream there as the pass writes them, same as the daemon’s.
- Memory got room to be memory.
memory_max_kbnow defaults to 32 KB, up from 8. Voice is unaffected — it reads the 2 KBvoice.mddistillate on every request and always did, so the small cap was never buying speed where speed is felt. The insight bundle was the other half of the mistake: learned memory and the house context shared one 4 KB budget with memory read first, so a document over 4 KB was truncated mid-fact and a full one starved the context entirely. Each gets its own budget now, sized so a full memory document arrives whole.
v1.17.0
“File into memory now” actually files, and the Chats rail is your chats.
- The consolidator gave Claude 120 seconds to rewrite the whole memory document plus the voice distillate. On a document with anything in it that was a coin flip — and losing it looked identical to a broken login:
timeoutkilled the CLI, the pass logged “Claude invocation failed (not authenticated?)”, the queue stayed put, and the daemon did it again five minutes later, forever, with the one explaining line sending you to redo a sign-in that was fine. A pass now gets 480 seconds — what an insight run gets, for the same reason — and says what actually went wrong: a timeout names the setting to raise, and any other failure carries Claude’s own last line of stderr instead of a guess. - The button stopped waiting for the pass it starts. A consolidation takes minutes; the request carrying it timed out long before, which is why the button appeared to do nothing and then reported failure while the pass was still running. It returns straight away now, the tab reports progress off the consolidator’s own lock, and pressing again joins the running pass instead of racing it.
- A terminal left open stays connected. Nothing pinged the browser half of the terminal’s websocket, so on an idle terminal the proxy in front — ingress, or Nabu Casa remote — closed it as idle after a minute or two, ttyd killed the session’s process, and the client reconnected for as long as the tab was open. Both halves are pinged now.
- The Chats rail is your chats. brAIn runs Claude Code in
/configfor voice, automation tasks and filing memory, and Claude Code files all of it where your own conversations go — so the rail filled with machine prompts, forty copies of the consolidator’s opening line, with yours somewhere underneath. Worse, switching back from the classic terminal adopted the most recent conversation, which was routinely the consolidator’s. Every background run now claims its session before it starts, so each row says whose it is: Yours is the default, Voice / Automation / Memory / Study are one press away with a count each, and switching back from the terminal only ever picks up a conversation of yours. - A quieter log. The panel logged a line per request, and an open panel polls: thousands of identical
200s pushing the one line that explained a failure off the top of the page. Successful polls are silent now, failures log as warnings, andlog_level: debugturns every request back on. ttyd’s own notice-level chatter goes the same way.
v1.16.0
- Your conversations, in a rail. On a screen wide enough (1100px and up), the chat tab grows a column listing every conversation in
/config— whichever face made it — with + to start a new one and the current one marked rather than hidden. Below that width nothing changes: 248px of conversations is most of a phone, so ⋯ → Conversations is still the way in. Nothing is reachable only from a screen you don’t have. - Which model, and how full. Under the composer, quietly: the model that is actually answering, and how much of the context window the conversation occupies —
42k / 200k context · 21%, turning amber past 80%. The token figure is the CLI’s own report of what it sent on the last turn, and what it sent is the conversation so far — a measurement, not an estimate. Cache reads count, because a cached prompt still occupies the window; it is only cheaper. A model with no published window reports its count and no percentage. - Dismiss, next to Ignore. A finding can now be cleared off the list without teaching brAIn anything: Ignore settles it — a line into memory, and the analyst told never to raise it again — while Dismiss just deletes the row, so the next analysis is free to find it again. That is what
forgetin the store has always done, and it had no button.
- A new chat does not lose the old one. “This one is cleared and Claude forgets its context” was overstating it — Claude Code keeps the conversation, it stays in the list, and you can reopen it. The prompt now says what is actually true.
- The last line of an answer is no longer flush against the composer. Scrolled fully down, the chat log left 8px, which reads as the message being cut off rather than as the end of it.
- The iPhone home indicator no longer overlaps the chat. The safe-area inset was on the composer, which stopped being the bottom-most element when the model line was added below it. It is on the container now, which is always last.
v1.15.0
- The full-screen terminal left the bar cut in half. Going full-screen and coming back gave you a sliver of a top bar that stayed a sliver on every other tab, until a reload.
.topbar’s height is--bar-h, and the panel writes--bar-hfrom the bar it just measured — stable at rest and wrong exactly once: the immersive terminal sets it to0, so on the way out the bar rendered clipped, the next measurement read the clipped height, and that became the new truth. Measured on a desktop viewport, the 56px bar came back as 1px. Measuring now clears the override first, and a zero is never written — the CSS class already says zero, and an inline one would outlive the class that justified it.
- The findings buttons say what they do, in fewer words. “Not a problem here” became Ignore, and “I’ve fixed it” became I fixed it — a row of four decisions is read at a glance or not at all. What each ending teaches brAIn is still there, in the tooltip, where an explanation belongs.
- The counts we advertise are now tested (
tests/test_documented_counts.py). “36 native tools” and “65 registry-management services” are derived from the registrations themselves, and every present-tense claim in the repo is checked against them. They had gone stale twice; the site said 56 for six releases after nine more shipped. - The docs screenshots are reproducible (
tests/manual/demo_panel.py) — the real panel booted against a seeded demo home with Claude stubbed out, so the pictures stay the actual product.
- The Answered filter, because memory is the record. Settling a finding writes a plain fact into
memory.mdand deletes the row; Answered then listed those answers again, which put a pile of dismissed cards next to a work list that is supposed to empty — and invited you to treat it as the record when memory already is. It and Everything are both gone; two chips remain, the work and what is waiting. The settled ledger is untouched and still stops the analyst re-raising next week what you answered today — it is simply not a view any more.
v1.14.1
- The guide opens with what brAIn is for. The Docs tab opened by describing what brAIn is made of. It now opens the way the READMEs and bruhautomation.com do — your house already has nerves, now give it a brAIn — followed by what it actually does. Five surfaces describe this add-on and they had drifted apart; they now open with the same words. Text only: no behaviour, no options, nothing to reconfigure.
v1.14.0
Switching to chat brought a fraction of the conversation — and on a phone, an insight card gave its title away to its buttons. Both are the same mistake: something that doesn’t matter crowding out something that does.
- A conversation carried across to the chat is now budgeted on what was said. Measured on a real transcript: 1,844 replay events, of which 1,701 were tool calls and their results — 92%. The window that carries a conversation across is 400 events and it was filled newest-first, so it carried 3 of the 17 things the person had said and 24 of 126 replies. That is what “not all the messages come over” actually was: they hadn’t. Every word either party said is kept first now, and the most recent tool calls get whatever is left — 17 of 17 and 128 of 128 on that same transcript. A call and its result are also kept or dropped together, because half a pair renders as a spinner that never stops.
- The docs tab could zoom the panel in and leave it that way. Not the docs — the search box. iOS Safari zooms the whole page in when you focus a text control whose font is under 16px, and does not zoom back out when you leave it; inside an ingress iframe that strands the panel at some arbitrary scale with the bar off screen. Text controls are 16px on touch devices now, asked for once rather than per control, so anything added later is covered by having been added. Pointer devices keep the density they were designed at.
- Long paths in inline code wrap instead of pushing the column sideways.
- On an insight card, the title gets the row. The head was one line — icon, category, title and six icon buttons — and the buttons don’t shrink, so on a phone they took about 250px of a 390px card and left the words 120px. The category spent that on three lines while the title was cut to “Upstair…”. Backwards: the eyebrow is the part you can afford to lose. The title now wraps to two lines and is never truncated, and the category is the one line that gives way.
- ⤢ Expand stays on the card; everything else moved into ⋯. Expand is the only button that acts on what is on screen rather than on the card’s definition. Regenerate, Edit, Give feedback, Add to dashboard and Delete now each have their name and a line saying what they do — which a row of six unlabelled glyphs never had room for.
- Tag filters are one scrolling row. Sixteen chips wrapping was four rows and most of a phone screen, before the first card. ✦ All is pinned to the left, so clearing a filter is always one press.
- The hint under the ask bar is one line naming the second verb, rather than a four-line paragraph teaching three features. The in-panel guide now describes the buttons that exist.
- The top bar’s ⟳. An unlabelled circular arrow that read like a page reload and in fact queued a Claude run for every card you have — minutes of work and a real bite out of the usage the pill beside it was reporting. Cards run on their own schedule, and ⋯ → Regenerate does one on demand.
v1.13.0
Findings end, instead of piling up. “I did it” beside “Not a problem” looked like two ways to make a card go away, and both of them left it lying there under a filter forever. That is two problems in one: you couldn’t tell which button to press, and pressing either one didn’t finish anything.
- A finding now ends, and ending it deletes the row. Every ending does the same three things: the answer goes into
memory.mdas a plain fact about your home, the wording is remembered so the same problem is never reported at you twice, and the card is gone. - The two buttons say what each one teaches brAIn, which is the only difference that ever mattered. ✓ I’ve fixed it — it was a real problem and it’s sorted now. Not a problem here — it was never a problem; this is normal in this house.
- An automated fix is the third ending, and works differently on purpose. The card stays and turns green with what brAIn changed and which files it touched. It altered something in your house, and news you haven’t read is not settled. ✓ Got it clears it once you have — no second memory line, because the fix already wrote one when it made the change.
- The Dismissed and Fixed filters are replaced by Answered — one line per ending, what it was, which answer you gave, when — with Let brAIn raise it again if you change your mind. That press stops the suppression and nothing more: nothing comes back on its own, the next analysis is simply free to find it, and if it has genuinely stopped happening, nothing does. Dismissals you made before this release are folded into the same record at startup, so nothing you had already waved off comes back.
- The terminal switch carries the conversation, both ways. Switching between Chat and Classic used to change the renderer and leave the conversation behind, which made two faces of one Claude Code feel like two rooms — and the separate “Continue in the terminal” button that did carry it only went one way, so neither control was obviously the one that moved you. The switch is the only control now: to Classic, the chat releases its session and the terminal opens already inside that conversation; back to Chat, it picks up whatever the terminal was last doing, transcript and all. One honest limit — only one Claude Code process can own a conversation at a time, so this is a hand-off, not a mirror. Your shell in the terminal is never killed to make it happen, and switching is refused mid-answer rather than throwing away an answer being written.
- The floating buttons over the terminal could vanish for good. Raising the software keyboard hid them — including ⤢, the way back out of a folded bar — and they only returned when something noticed the keyboard had gone, which on iOS frequently never fired. They are never hidden now: the escape hatch may not be behind the state it escapes from.
- The slash palette stopped filling in. Typing past the end of the filtered list left the highlight pointing at a row that no longer existed, and Enter/Tab silently did nothing from then on.
- Two scrollbars in one pane. The page scrolled behind the terminal, and the memory editor scrolled inside its own already-scrolling box.
v1.12.1
Memory was never being consolidated at all. If your memory document had gone stale and the queue of things waiting to be filed only ever grew, this is why — and it had been true for weeks. Update, and the facts that piled up while it was broken are filed on the next pass. Nothing was lost; it was queued.
- The consolidator’s lock was never obtainable, so no consolidation ever ran. It took the lock with
flock -w 600, and-wis util-linux’s flag. This add-on runs on Alpine, whoseflockis BusyBox’s, and BusyBox accepts only-sxun. Handed an option it doesn’t know, BusyBox prints usage and exits 1 — which is the exact statusflockuses for “the lock is held”. So every pass, from the daily daemon and from ⇪ File into memory now alike, failed to take a lock nobody held, decided another pass must be running, and did nothing. The inbox grew, the document went stale, and the only trace was one line in the add-on log. Only portable flags are used now, and whether flock can be used at all is probed with the same flag the real call uses. If it genuinely can’t lock, the pass runs anyway and says so, because refusing forever is the worse failure. (The guards added in 1.11.2 were real — they just sat behind a gate that never opened.) - A wedged consolidator is now visible. The reason this hid for weeks is that nothing on any screen said anything; the queue just sat there looking like a queue. The Memory tab now says so when facts have been waiting appreciably longer than the daily pass, with what to try and where to look if that doesn’t clear it. It deliberately doesn’t detect this bug — it detects the symptom every cause of it shares: facts waiting, and no pass landing.
v1.12.0
The two terminals are now one terminal with two faces. Chat and Classic already ran the same Claude Code. What they did not do was let you move a conversation between them, which made them two places rather than two views.
- ⟲ lists every conversation in the project directory — started in the chat, started in the terminal, it makes no difference — with its opening line and when it was last touched. Pick one and it replays into the chat pane and carries on. Not a blank box with a promise that Claude remembers: the actual conversation, rendered through the same code as a live turn.
- Continue in the terminal now opens the terminal inside the conversation rather than handing you a command to paste. The chat releases its session, leaves a handoff for the terminal’s launcher, and — if the terminal is already attached — opens it in a new tmux window there and then. A terminal that has never been opened still comes up in the right conversation, because the handoff is a file its launcher reads rather than keystrokes typed at whatever happens to be in front. It expires after ten minutes, so a restart tomorrow does not silently reopen today’s chat.
- 💬 Discuss hands a finding to the chat with everything it knows about it — the detail, the suggested fix, the entity, the severity — and asks Claude to say plainly whether it really is a problem here. The prompt tells it explicitly not to change anything: “explain this to me” and “go change my house” are different consents, and ✦ Fix it is still the only button that gives the second. While you’re discussing, a strip above the composer names the finding and keeps Fix it, I did it, Later and Not a problem one press away, and it survives a reload.
- ⏰ Remind me later takes a finding off the list for an hour, until tomorrow, next week or next month. It is deliberately not a status change: dismissing is permanent and fed back into every future analysis, and using that for “not right now” would quietly throw away a real problem you meant to come back to. It sits under a Later filter while it waits, with the date it returns and a “bring it back now”.
- The slash palette knows
brainandha. Type / and you get Claude Code’s commands; type brain or ha and you get brAIn’s own —brain memory add,ha reload, all of them, with the same descriptions and argument hints the dispatchers print. The list is parsed frombrain helpandha help, so a subcommand added to a dispatcher appears without anything in the panel being touched.
- The chat had five buttons floating over your output. Five translucent squares stacked on the text you came to read is exactly what this view exists to get away from. There are two now: ⤢, which keeps its own place because it is also the way back from a folded bar, and ⋯, which holds New chat, Conversations, Session details and the switch to the classic terminal.
- The top bar stopped saying the same thing twice. “Usage budget reached” was a chip sitting immediately beside a usage pill already reporting the very number it was about — and on a phone the pair wrapped the bar onto a third row. The pill carries that state itself now: its dot goes warning-coloured, and pressing it says plainly that automatic insights are paused, what the budget is, and when the window rolls over. What is left beside it is the one thing a press can undo: Auto insights off.
- Any control other than a top-bar chip that opened a popover had it closed again by the same press — the dismiss-on-outside-click listener only recognised the chips as legitimate openers.
v1.11.2
Home memory cannot be erased by a consolidation any more. This is the important one: a consolidation pass could replace a year of learned facts with the blank template, and nothing would object.
- A consolidation that came back with an empty document is refused. The consolidator asks Claude for the whole updated
memory.mdand then checks the answer before writing it: not empty, still has its##headings, still under the size cap. A document that came back as nothing but those headings passed every one of those checks — it is not empty, it has headings, and it is very much under the cap. So a pass where the model rewrote instead of merging wiped the lot. Two guards now stand in front of that write: coming back with no content at all, over a document that had some, is refused outright at any size; and losing most of the content in one pass is refused while the document is comfortably under its cap, which is the only case where consolidation should ever shed lines. Either way the document is left exactly as it was and the inbox stays pending, so the next pass tries again. A stale memory is recoverable; a wiped one is not. - A failed write no longer eats the facts. The script runs without
set -e, so if writingmemory.mdfailed — a full disk, a permission problem — execution fell straight through to the step that archives the inbox. The document would be unchanged and the queue emptied: the one combination where nothing anywhere says something went wrong. The write is checked now, and a failure leaves both alone.
- The Memory tab says when it is filing. Consolidation runs daily, and early once the queue passes 20 facts, and none of that reached the panel — only passes you started with the button did — so the queue could empty while you were looking at it with nothing on screen accounting for where the facts went. The tab now shows a running pass whoever started it, with a line saying whether it is yours or the schedule’s.
v1.11.1
The terminal now stands where the chat does. Claude Code files every conversation under the working directory it was started in, and claude --resume only lists the ones belonging to the directory you are standing in — so when the two faces of one tab disagreed about that directory, neither could see the other’s conversations.
- Every session the terminal starts is pinned to
/config. The chat terminal always ran there; the tmux session inherited whatever directory the add-on’s init happened to give it, which is why a chat conversation could not be resumed from the terminal. The same directory is what makes/config/CLAUDE.mdload and what makes/config/.claude/settings.local.jsonthe project settings the whole add-on is documented as running under, so inheriting it by luck was never a good idea either. - No more price tag on a subscription. Every answer ended with something like
$0.012— what those tokens would have cost had you bought them from the API. On a Pro or Max plan that is not a charge, and printing it after every message is a number that looks like money and isn’t. The CLI tells us which case it is, so the figure now appears only when an API key is genuinely being billed per token. On a subscription you get the duration and the turn count, which are the parts that mean something.
- Slash commands in the chat terminal. Type / and the palette lists what your install actually has — including anything in
/config/.claude/commands— with descriptions and argument hints. ↑/↓ to move, Enter or Tab to pick. The list is never hardcoded, so a command you add appears without brAIn being told about it. A few commands are REPL-only (/helpamong them) and say so politely rather than failing. - ⓘ shows the session id, the model, the project directory and how you are being billed, with Copy the command and Continue in the terminal. The second one releases the session first, because while the panel holds a conversation open the terminal is being asked to resume something still in use.
v1.11.0
The terminal stops being a window inside a window. A terminal is a grid of fixed-width cells. On a phone that grid is about 40 columns wide, and a grid cannot reflow — so sentences broke mid-word, a single tool call spent twenty lines saying what one line could say, and the whole thing sat inside ttyd inside tmux inside an iframe.
- The Terminal tab has two faces, and a button on the tab switches between them (⚙ Settings has the same control). Both run the same Claude Code, on the same login, in the same
/config, under the same permissions — the difference is entirely how you see it. - Chat is the new default: Claude Code’s own streamed output rendered as ordinary DOM. Text reflows to the screen it is on. Code blocks keep their grid, inside their own horizontal scroller, so a 200-column log line never makes the page slide sideways. Tool calls fold into one line each —
Read /config/automations.yamlwith a dot that goes green or red — and a failed one opens itself, because it is the reason the next thing Claude says will look strange. Reasoning folds away behind a “Thinking” line. - The composer is a real text box, so dictation, autocorrect and selection behave — there is no hidden terminal helper element to fight with. ⏹ stops an answer and + starts a new chat; stopping asks the CLI politely first and kills it if it does not answer, and either way the conversation survives, because Claude Code is what persisted it. The transcript survives a reload, a locked phone, and an add-on restart.
- Classic is the terminal exactly as it was — ttyd over tmux — and is the right answer for anything that draws its own screen: a TUI,
htop, an installer, or running shell commands yourself.
- Nothing about what Claude may do changed. The chat session runs in
/configunder the samesettings.local.jsonpermissions as the Assist listener, the Automation listener and the Findings fixer, so there is still one answer to “what may Claude do here” rather than two.
v1.10.0
The bar is one size now, and every control in it does its own job.
- The third bar shape is gone. Between roughly 960 and 1240 pixels the top bar had one row with the tab labels deleted and the tabs shrunk to bare glyphs. That is the width a laptop with the Home Assistant sidebar open actually renders at — so the compromise was the shape most people saw, and widening the window made the tabs grow, which reads as a bug whatever the intent. There are two shapes and no third: one labelled row at 1240px and up, and the two-row bar below it, with all five tabs still named. Tabs stop growing at 168px and centre themselves.
- The usage pill opens its own numbers. Press it and you get both windows with when each one resets, and what the budget actually gates. A press rather than a hover, because the reset times used to live in a tooltip — a fact that exists and cannot be read on a phone, which is where that pill is most often the only thing worth reading.
- “Auto insights off” is now the switch. One press turns them back on and the chip goes away, because the thing it was reporting is no longer true. A usage budget that has been reached isn’t a switch, so that one explains itself instead. ⚙ is the one route to Settings — three controls used to open it, so a bar reporting three different things answered all of them with the same dialog.
- The terminal gets the screen back on a phone. With the keyboard up it was getting about a third of the display. The bar now folds away while you’re typing and comes back when you dismiss the keyboard, and ⤢ over the terminal folds it away for good — the same button brings it back. tmux also drops its status line below 90 columns.
- The documentation says what brAIn actually is — rewritten around the whole capability rather than around three components: brAIn administers Home Assistant, every entity, device, area, floor, label, dashboard, helper, automation and add-on, with the native tools, the registry services and the shell all in one place. A new What brAIn can do section opens the in-panel guide, and references to the two add-ons brAIn replaced are gone.
v1.9.0
A top bar you can actually hit. The bar was a fixed 48px row at every width, and it stayed one row by deleting text until it fit — tab labels first, then the words inside the status chips. On a phone that left five unlabelled glyphs and a bare amber dot, with the only explanation in a hover, on the one device that cannot hover.
- The bar has two shapes. On a desktop it is a single 56px row. On a phone the tabs move to a full-width strip of their own with each name under its icon, and every target — tab, button, status pill — is at least 44px. Nothing hides its words to fit any more; what gives way is the row.
- The usage pill says which number is which. It read
19% · 100%: two percentages, a dot between them, and nothing saying that the first is your 5-hour session and the second is your week. It now reads Session 19% · Week 100%, labelled in the bar itself. The amber dot beside it is gone — that was the “auto insights off” / “budget reached” chip with its words hidden, which is a warning that declines to say what is wrong. Hovering the pill gives you the reset times and nothing else, rather than reciting three facts already on screen. - The Memory tab stops repeating itself. “Already in memory — 23 discoveries” is gone. Once a discovery is filed it is part of the memory document on the right, and that is where you read it, edit it, or take it out; listing it a second time underneath the queue meant a drained queue never looked drained. Nothing was deleted — the dedup ledger still holds every announced fact, so brAIn still can’t tell you the same thing twice. The four explanatory paragraphs came down with it; the long version is still in the Docs tab.
- Nine Power Tools services, closing every gap where you could create something and then never change or remove it.
rename_labelandupdate_label— a label was create-only, and its colour, the thing a label is mostly for, could not be changed after the fact.delete_device, withdry_runto preview the entities that go with it and name the config entries that would recreate it, so a delete that won’t stick is visible before you make it.delete_orphaned_devices, the device counterpart of the entity cleanup, dry-run by default.delete_integration, removing a config entry with its devices and entities — disable was reversible and there was no delete at all.set_area_iconandupdate_floor, for attributes that could be set at creation and never afterwards. Andrename_person, for the same reason as all the others. update_*services write only the fields you actually name, so changing a label’s colour doesn’t blank its description. That is now a test, along with the rule these services came from: every registry object brAIn can create, it can also rename and delete.
v1.8.0
It’s brAIn. The name is spelled that way everywhere now — add-on, panel, integration, sensors, CLI help, and these docs. The wordmark never needed changing: the gable already doubles as the A, and the A and the I were already the one part drawn in the accent colour.
- ”⇪ File into memory now” empties the list. It filed the queue and then showed you the same discoveries with the same “2 things waiting” underneath, because the list was reading the dedup ledger — the record of what has already been announced, which by design keeps entries forever. It is split now: Waiting to be filed is only what is genuinely still queued, and everything already folded into the document moves into a collapsed Already in memory group below it. ✕ still works in both.
- A pass that filed nothing no longer reports success. The consolidator exits 0 in cases where it deliberately keeps the facts, and being skipped because another pass held the lock exited 0 too. The queue is now counted either side of the pass, so “the queue didn’t move” and “another consolidation is already running” are things the panel can tell you instead of “Filed 2 things” over an unchanged list.
- The usage pill shows both windows —
19% session · 64% week. The seven-day limit is the one that actually ends your week on a Claude plan. Reset times moved into the hover, where a value that changes once per window belongs; the numbers, which change all day, stay in the bar. The ⚙ dialog states the week too. Both readings come from your Anthropic account, so they need a subscription login rather than an API key. - The “Claude · subscription” pill is gone. A green pill labelling a state that never changes, in a bar where width is the scarce thing. The auth chip now appears only when there is something to say — verifying, failed, or not connected — which paid for the second usage number twice over. On a narrow phone the weekly number steps aside, and below 410px so does the whole pill if a trouble chip needs the room.
- Display names follow the spelling. The conversation agent, the system health sensor and the usage sensors read “brAIn” in Home Assistant. Entity IDs are unchanged, so nothing in your automations or dashboards breaks.
v1.7.0
Findings — memory you can act on. Memory tells you what is true of your home, and a guess asks whether brAIn has something wrong. Neither had anywhere to put the third thing: something that is broken.
- A Findings tab, and it is a work list. A battery that died. A sensor that has read the same value for six days. A device stuck unavailable. An automation whose trigger entity was renamed, so it can never fire again. Insight runs and study sessions both file them, and the same problem in different words is recognised and dropped, so a given problem is reported exactly once.
- ✦ Fix it sends Claude to make the change in your actual Home Assistant: confirm the problem is still real, find the cause rather than the symptom, make the smallest change that resolves it, verify it took, and report exactly what it touched. Bounded hard — one finding per run, never deletes what it didn’t create, never restarts Home Assistant, never touches secrets, and nothing runs until you press it.
- Not a problem dismisses a finding permanently, and the dismissal is fed into every future analysis. If the garage freezer is supposed to sit at -30°C, one press ends that conversation for good. Anything needing hands is marked needs you rather than offered a fix, and ✓ I did it closes those.
- A second verb in the ask bar. Start a line with “learn about…” or “study…” and brAIn runs a study session instead of drawing a card, digging through the registry, history and long-term statistics for that corner of the house. It was previously reachable only from the terminal, which meant nobody ran one.
- ⇪ File into memory now on the Memory tab runs a consolidation immediately rather than waiting for the daily one — same script, same safety checks.
- Tags are yours to edit. Press ✎ on a card’s tag row to drop a tag or add your own. What’s stored is a diff, not a list: your removals stick across regeneration, but a genuinely new tag a later run discovers still appears.
- The header carries the real wordmark as one piece of art rather than the gable beside live text, in three brand roles so a single file works in both themes.
- Learned and found are separate. What a run learns about the home (durable facts → memory) is now a different field from what it finds wrong (→ Findings). They were one, which is why nothing was ever actionable.
- The “New insight” dialog. Asking a question already made a card, and any card becomes recurring with + Make recurring — a blank prompt dialog was a second, harder path to somewhere you had already been taken.
- The removed-cards graveyard in ⚙ Settings. That belonged to a version that shipped nine cards to every house; this one proposes cards for your home, so the way to get one back is to ask for it again. ✕ now means the same thing for every card: gone.
v1.6.0
A new mark. brAIn’s logo is now a descendant of the BRUH Automation logo rather than a cousin of it: the BR ligature, the gable and the signal motif are lifted unmodified from the parent mark, and only the A, I and N are newly drawn, on the parent’s own ratios. The gable is the A. The old mark was a generic neural-mesh glyph that could have belonged to any AI product — and was never really chosen, it won by being first in a folder.
- New art everywhere it appears — the panel’s top bar, the favicon, the add-on store icon and logo, and the home-assistant/brands assets, all re-rendered from one set of SVGs so they can’t drift.
- The sidebar icon is
mdi:home-analytics, replacing an icon picked to rhyme with a mesh that no longer exists. - Wide lockups are 4:3 rather than 640×200 — the new mark is near enough square that a banner shape either stranded it in empty plate or cropped it.
v1.5.1
- The header wrapped onto a second row on a phone. A
flex-wrap: wraprule left over from the old two-bar chrome spilled the auth and usage chips out of the fixed-height bar, and the icon-only breakpoint sat at 780px when the full bar needs 995px — leaving tablets and half-width windows overflowing by up to 212px. The bar now sheds text in three measured steps (chip sentences below 1024px, tab labels and the wordmark below 780px, tighter padding below 400px), verified at 24 widths from 320px to 1440px, and nothing in the bar is allowed to shrink.
v1.5.0
No default cards — it learns your home first.
brAIn used to ship nine cards (Overview, Energy, Climate, Lighting, Security, Presence, Media, Device Health, Automations), all enabled from the moment you installed it. They generated before brAIn knew anything about the house, so they said generic things about a home it had never looked at — and cost tokens doing it, on every schedule, forever.
- A fresh install now has no cards at all. The first run studies the home — naming, occupancy, energy, climate, device reliability — and only then proposes cards grounded in what it actually found, each with a one-line reason citing the evidence. You pick which to keep. Nothing generates, and the scheduler stays idle, until you do.
- There is no canned fallback. If the home is too sparse to learn from, brAIn says what’s missing and stops. Generic cards about a house it can’t read would be noise on every run, and would teach you to ignore the dashboard.
- The flow is resumable — close the panel mid-study and come back — and re-running it never re-studies a topic it already covered, because a study session is expensive.
v1.4.1
- Answering a guess from an insight card never settled it. When hypotheses replaced open questions in 1.3.0, the Memory tab was updated but the card renderer and its endpoints were not. Cards still showed a free-text “Answer” box — asking for an essay where the answer is yes or no — and the handler wrote to the old question ledger instead of the queue. The card looked answered while the guess stayed open in Memory until it expired a fortnight later. Cards now show the same two-tap ✓/✗, and settle the queue by resolving the claim’s text (a card carries the text, not the id).
- Removed the “Answered questions” section from Memory. It belonged to the model this release replaced, rendered
Q: … A: …— exactly the format removed from memory — and nothing populated it any more.
v1.4.0
A single, compact bar — and the guess you click is the guess that gets settled.
- Clicking ✓ on the second or third pending guess settled the first one. Hypotheses used the current epoch second as their id, and a study session proposes several claims inside the same second — so they collided, and settling matched whichever came first in the file. Ids are now unique per entry. (
knowledge_storehad guarded against exactly this; the hypothesis queue didn’t inherit it.)
- One 48px bar instead of two stacked bars plus a row of labelled buttons. Roughly 110px of fixed furniture sat above every view, which on the terminal — where each pixel is a line of output — cost real content. The bar now carries the mark, the tabs, status and actions.
- Monochrome line icons, inline SVG inheriting
currentColor, so they follow tab state rather than competing with it. Azure is the only colour in the chrome and it marks only what is active. - Toolbar actions are icon-only — the labels were noise beside four tabs. On narrow screens the tab labels drop and the icons stay, so all four still fit.
- The Memory tab shows a count when guesses are waiting. A guess nobody sees is a guess that expires unanswered.
v1.3.0
Guesses instead of questions. Insight runs no longer ask open-ended questions. They state what they believe, phrased for a yes/no: “The garage fridge is meant to run 24/7 — right?”
- Two taps settle it. Yes files it as a plain memory line; No records a dead end that is never revisited. The cap is enforced in code, not just asked for in the prompt — a model that ignores the budget still cannot grow the queue. Three open at once, 14-day expiry, and a claim already proposed is never proposed again in any wording.
- Turn limits were too tight, and failed badly. A turn cap does not degrade — it truncates. A run that hits one stops mid-thought and produces nothing parseable, so the tokens are spent and the result is lost. That made a tight cap the most expensive setting in the add-on. Study sessions: 14 turns → 60, timeout 10 → 30 minutes, and
study_max_turns: 0now removes the cap entirely.brain ask: 8 → 30. Automation tasks: 10 → 30. Voice: 5 → 8, deliberately still modest, because latency is the product for voice. - Hitting the limit is now reported as hitting the limit, rather than as unparseable output — blaming the model for a limit we imposed sends you looking in the wrong place. Study prompts also tell the model to land its result if it senses it is running short, so a long session degrades to partial instead of losing everything.
- Logbook events. Every new fact fires
brain_learned, so “brAIn learned: the hallway sensor drops offline around 2am” appears in your home’s timeline next to lights and doors. sensor.brain_facts_learnedandsensor.brain_last_learned.binary_sensor.brain_waiting_on_you— on when a guess needs an answer, with the text inpending. This exists to be automated: a guess sitting in a panel nobody has open expires unanswered, but pushed to a phone it costs one tap.brain.studyservice — with a topic, or without one to study whatever has gone stalest. Returns immediately; results arrive in memory, not in a response./learnand/memoryslash commands in the terminal, where you can watch a session work and correct it mid-flight.
v1.2.0
- A Docs tab, next to Memory: getting started, the three tabs, how memory works, the command line, undo, voice, cost control, and troubleshooting. Searchable, with the matched term highlighted in the page. The nav, the search index, and the body all come from one source, so navigation can’t drift out of sync with the content.
brain doctorreported the Assist worker pool as failing when it was healthy. The probe was pinned to port 8099, which the panel took over when the two add-ons merged; the panel answered — with a 404 — so the check failed against a perfectly working pool. It now reads the port the pool publishes instead of assuming one.brain doctorsmoke-tested CLI names that no longer exist (ha-entity,ha-addon,ha-service,ha-yaml-check), producing five warnings for tools that were fine. It now exercises thehadispatcher.- The generated
/config/CLAUDE.mdstill documented the retired hyphenated commands, includingha-backup, which no longer exists at all. That file is how Claude learns its own tooling, so a stale entry is a command it will actually try to run. Rewritten for the two dispatchers, and a test now fails if a retired name reappears. - Removed the Memory button from the header — it duplicated the tab.
v1.1.1
- Signing in once is now enough. Signing in through the panel still left the terminal asking for a login. Credential sharing was built when Terminal and Insights were separate add-ons and only ran one way: the terminal’s
ha loginpublished a credential the panel read. Merged into one add-on the panel became the primary sign-in surface, so the arrow has to point both ways. A single resolver now hands whatever credential exists to the CLI — used by both theclaude-runwrapper and interactive shells. If the CLI already holds its own OAuth login it is left strictly alone: it refreshes that credential itself, and injecting a token over the top would break the refresh.
v1.1.0
The panel is finally one product.
- Three tabs: Insights, Terminal, Memory. The terminal is the same ttyd the add-on already ran, reverse-proxied through the panel, so it is a tab rather than a second sidebar entry. The frame only connects when you first open the tab — no shell session is started for someone who never does.
- Memory is a tab, not a dialog. The same pane, promoted out of the modal it was hidden in.
- The panel still said “BRUH Insights” in its header, and drew the Insights bar-chart glyph. The wordmark is split across HTML tags (
BRUH <span>Insights</span>), so the rename never matched it. It now reads brAIn with the neural-mesh mark. A test now strips tags before checking, so this class of miss can’t come back. - Several hints told you to go run a command in “the brAIn add-on” — from inside brAIn. They were inherited from when Terminal and Insights were separate. They now point at the Terminal tab.
- Retired CLI names in the UI.
ha-share-loginandha-memoryno longer exist; the panel referenced both. - A new agent defaulted to the name “Claude Agent” instead of “brAIn Agent”.
- Added
logo.png/logo@2x.pngfor the home-assistant/brands submission. Until that PR merges, Home Assistant has no artwork for thebraindomain and shows the raw domain beside the name — which is why a fresh install reads “brain brAIn”.
v1.0.1
- The panel’s login failed with
su-exec: claude: No such file or directory. The CLI was looked up with the root user’sPATHand then executed as theclaudeuser. The binary lives at/root/.local/bin/claude, which is on neither user’sPATH, so the lookup fell through to the bare nameclaudeand su-exec couldn’t find it. The panel now prefers theclaude-runwrapper and otherwise resolves an absolute path.
- BRUH Terminal and BRUH Insights are removed. brAIn replaces both; their test suites now cover brAIn.
- Renamed the files that were ours rather than Claude Code’s:
claude_client.pyis nowpanel/engine.py, and the session picker and auth helper arebrain-menu.shandbrain-auth-helper.sh.CLAUDE.md,CLAUDE_CONFIG_DIR, theclaudeuser, and theclaude-runwrapper keep the name — they are Claude Code’s own file, env var, user, and binary.
v1.0.0
First release. brAIn replaces BRUH Terminal and BRUH Insights, which are now deprecated. It is a clean install — there is no migration from either add-on.
- The terminal and the insights dashboard now share a process. They were two containers, which meant authenticating Claude twice, two Claude clients, two settings stores, and two writers racing on one memory file. Now it’s one of each.
- One ingress panel serves everything. The panel owns port 8099 and reverse-proxies
/terminal/through to ttyd (HTTP + WebSocket), so the terminal is a tab rather than a second sidebar entry. Port 7681 is still published for direct access.enable_terminal/enable_insightsturn either face off. - An edit journal, replacing git backups. A
PreToolUsehook snapshots a file’s prior contents before Claude writes to it, andbrain undolists those edits in plain English and restores one. It records only what Claude touched, lives under/dataso it never pollutes the config directory, and prunes onedit_journal_days(default 14).
- The assist worker pool moved to port 8098, since 8099 now belongs to the panel. Nothing hardcodes it — the integration reads the port from the endpoint file the pool publishes.
- Integration domain is
brain: services arebrain.send_prompt,brain.run_task, and the rest, including all 56 Power Tools. Shared state moved from/config/.bruh_claude/to/config/.brain/, environment variables use theBRAIN_prefix, and the conversation agent appears as brAIn in Settings → Voice Assistants. assist_learningis now justlearning— it governs everything brAIn learns, not only the voice channel.- Fourteen
ha-*scripts collapse into two dispatchers, split by what they act on:brainfor its own faculties (memory,learn,ask,undo,doctor) andhafor Home Assistant operations (log,reload,entity,service,addon,notify,share,check,context). If a pre-existinghacommand is ever found onPATH, brAIn installs its own ashassinstead rather than shadowing it.
- Git auto-backup —
auto_backup,backup_interval_minutes, the 30-minute commit watcher, and the.gitignoremanagement that came with them. Versioning the whole of/configinside/configduplicated what a real Home Assistant backup already does, and the repo it grew was then swept into those backups. Existing/config/.gitdirectories are left strictly alone; delete yours if you don’t want it. Thegitbinary is still installed — it’s useful in a terminal.
The complete, unabridged history — including the full detail behind every entry above — lives in the CHANGELOG.md in the add-on repository.
Before brAIn
brAIn is a clean start, so its version numbers begin again at 1.0.0. The two add-ons it replaces kept their own histories, preserved in full:
- BRUH Terminal changelog — 1.0.0 through 3.6.0
- BRUH Insights changelog — 1.0.0 through 1.7.0