Fleet Runner 0.8.59
- Sessions can open a plain shell on this builder: a real bash command line with no project and no agent, for the account's owner, behind the PIN.
Each version of Fleet Runner, the desktop app that runs agents on your own computer. The latest is v0.8.59, published October 27, 2026. What changed in Loki itself is on the changelog.
The Terminal's new + can start another session on a busy project. The runner used to launch such a session straight into the project's one checkout; it now gives it a git worktree, the same isolation parallel runs already had.
0.8.41 taught the runner to open web forms in a browser window. That window is part of the desktop app, and the cloud builder — the same runner, without a screen — tried to load it at start-up and could not. It now loads only when a form actually arrives, which only ever happens on a computer.
Some steps need you on purpose — a passkey, a final Submit. Everything before them shouldn't: Loki now does the finding, typing and ticking, outlines the button that is yours, and never presses it.
Claude Code records its permission mode on every message you send. The runner now passes it along with the conversation, so Loki can show it beside the message box, the way the Claude app does.
Screenshots used to be described in words by a free vision model before the agent saw them; when that model was busy, the agent got an error instead of the image. The runner now writes each screenshot to ~/.loki/attachments and gives the agent its path.
Claude Code names its log folders by turning every character that is not a letter or digit into a dash. The runner only did that for slashes and dots, so some projects showed an empty conversation forever. Token accounting used the same rule and is fixed with it.
Claude Code keeps every session in a log file. The runner follows the newest log for the folder the agent runs in and posts messages and tool calls to Loki, which renders them as a conversation beside the raw terminal.
Claude Code reports an open dialog itself (status waiting, 'dialog open'); the runner reads that instead of guessing from the screen. On the cloud builder the runner also now looks up Claude's session by the folder the agent really runs in, so its check that the agent started working is no longer a guess.
The runner now tells Loki when it started. Every agent session lives inside the runner process, so a session last heard from before that moment has ended. If such a run had already pushed work to the repo, it is recorded as partial rather than failed.
A box-* runner that registered bridge presence as local made applyHeartbeatExpiry clear both channels. Presence channel is inferred from the runner version (or LOKI_RUNNER_PRESENCE_CHANNEL) so it matches the runtime-state heartbeat.
Quota detection now reads only what the agent printed after the prompt was submitted, ignores lines that are just the prompt echoed back, and defers to Claude's own session status. A run is still closed when the agent really hits a usage wall.
These are transitive dependencies, libraries pulled in by other libraries rather than chosen directly, which is why no automatic update ever proposed a fix for them. They ship inside the runner, so the patched versions only reach a machine through a release like this one.
Dependency maintenance only — nothing about how the runner behaves changes. These updates sat in three separate pull requests for up to fifteen days: the auto-merge sweep had deadlocked, and each was additionally red on the release-drift gate, which asks any change reaching a machine to bump this version and add the entry you are reading. Shipped as one release rather than three so operators get one update, not three identical-looking ones.
Cosmetic, but it shipped in every installer: the only place the retired identity was still compiled into a binary rather than rendered from content.
Prompt delivery and model generation are separate facts. Fleet Runner now checks the provider screen before it tells Loki that generation began.
The runner still keeps one efficient PTY subscription per session; it now replays that retained screen for each new viewer.
Proven through the production feedback loop: Grok was visibly reading and editing in Loki Terminal while the old verifier had already marked the run Failed.
A rename, not a behaviour change. It ships as its own version so the tag, the /releases page and the machines that auto-update agree on what they run.
Fixes a real thirteen-minute wait on 2026-09-12: a dispatched fix sat at a Claude sign-in prompt and the only way to discover it was to open a terminal by hand. The authentication check already existed but ran once, in an 8-second window at inject time.
Projects Loki set up on the cloud builder are already routed there, so this is the belt to that braces: if such a dispatch reaches a laptop anyway, the failure names its real cause in one line instead of looking like a broken agent.
Server side ships in the same Loki deploy (the box runner restarts once idle). A desktop Fleet Runner on 0.8.20 keeps working; its runs simply show the pre-heartbeat phases until it updates.
Follow-up to 0.8.19. No behaviour change for a running fleet; this release exists because the desktop tree changed and the changelog is the single source of truth for what shipped.
One substrate. Since 0.8.3 the owned terminal was the default with zellij kept as a fallback for tabs it could not find, and the fallback is where every stuck dispatch ended up: a keystroke typed into a tab named 'Tab #9', a cold-start that spawned a session no terminal could show, a desired-state restore that reproduced the box's fleet on a laptop. This release deletes the fallback rather than hardening it again.
This closes the identity gap between Implement and Watch: the database session id drives resume, while a tab remains only the transport used to display and type into that session.
Dependabot bumped @types/node (#411), which edits desktop/package-lock.json — a file that determines what gets built into the runner. Nothing bumped the version alongside it, so main went red on the release-drift gate and the merge queue stopped: the sweep only merges onto a green base. The gate was right. A lockfile change with no release is a change that lives on the server and on no machine, which is the failure the gate was written for after six commits went missing at v0.8.12. Worth noting the shape: a dependency bot can trip a release gate, and its PR will never carry the bump itself.
Seen on a real dispatch: “launched claude (pty) + injected … Retry, or switch the project agent away from grok if this repeats.” One sentence naming the agent that ran and, as advice, one that did not — a literal left over from when grok was the default, in a message that already had the right agent in scope. Small, but it is the sentence you read when something has gone wrong, and advice about an agent you are not using undermines the diagnosis around it. A gate now requires any failure message that names an agent to interpolate the real one.
The 41-repo fleet moved from the personal catomean account into the bitbaum GitHub org, and desktop carried two literal owner references (the issue-report link, the auto-updater's feed owner) that the sweep updates alongside every other repo. Same shape as 0.8.14 four commits ago: an owner literal in desktop/ changes, the release-drift gate catches it, a release ships. This one is the last time it should be an account name at all — the org is the stable home going forward.
The GitHub account this project lives under was renamed, and two files in desktop/ carried the old name — an update URL and a package reference. GitHub redirects repository URLs, but only until somebody else claims the freed name, so a redirect is not something an auto-updater should depend on. Cut as its own release because the check that fails when desktop code changes without a version bump is exactly the check that caught it, four commits after it was written to prevent this.
Everything here was merged between 14 and 26 August and had been sitting on the server, unreachable by any machine, because a release was never cut. Fleet Runner ships only when a fleet-runner-v tag exists, that tag was minted by hand, and nothing checked that anyone had done it — so seven changed files reported no problem at all while going nowhere. CI now fails when desktop code changes without a version bump, and mints and publishes the tag itself once main is green. This release is the backlog that gap accumulated.
Local counterpart to the web-side tab-truth work. The join reads the ZELLIJ_PANE_ID that zellij exports into each pane and the agent CLI inherits (via /proc/<pid>/environ), then resolves it against the session's own metadata — pane id to tab position to tab name. This replaces name-matching, which cannot work for a default-named tab: "Tab #3" shares no text with the project directory, so every match fell through silently and the runner published an empty pane list. Resolution order is pane id, then config entry, then directory basename, and any parse failure yields an empty map. Needed a release because the web deploy updates the cloud builder but cannot update a desktop app.
Recorded here after the fact: this release shipped on 2026-08-04 but was never added to the timeline, so /releases and the footer version pill both kept claiming 0.8.9 while operators were running 0.8.11. The release itself restored the full three-platform matrix (#157) after a period when only Linux was being produced.
P3 terminal interactive parity. A non-durable rawkey/resize event rides the existing bridge NOTIFY → SSE channel (no per-keystroke DB rows, no command-claim); the runner dispatches it to writeRawKey/resizePty, writing bytes verbatim into the agent's owned PTY. Strictly additive and independent of the autopilot command-drain path. Verified end-to-end: typed in the browser terminal, echoed from a runner-owned PTY with no zellij dependency; killing an agent leaves the runner green (PTY isolation confirmed). Linux (AppImage + .deb).
Reliability: the wait=0 command poll had no request timeout, so a connection left half-open by a backend restart could hang the poll forever — the poller went silent, the process stayed alive (so nothing restarted it), and the autopilot loop stalled. Bounded the poll with AbortSignal.timeout(20s); on timeout it backs off and retries instead of hanging. Pairs with the supervised systemd service (Restart=always) for end-to-end 'never silently offline'.
Injection fix. Claude's TUI treats a big multi-line write as a bracketed paste and absorbs a trailing carriage return into the paste buffer instead of submitting — so the prompt sat in the input unsent and every dispatch reported “the agent didn't pick up the prompt within the window.” Fixed by sending the submit Enter as a separate keystroke after the paste settles (two nudged CRs at 250ms/800ms). Found by driving kivvi end-to-end and reading the live PTY.
Reliability release for the autopilot loop. Three root causes of the 'nothing works' freeze: (1) the live-terminal peek ran a synchronous zellij dump-screen that blocked the runner's single event loop and starved command acks → every command was re-served forever; fixed by streaming only owned PTYs (async, in-memory). (2) the status push had no request timeout → one hung request killed the heartbeat. (3) runtime state was only pushed every 5 min → launched agents took minutes to appear; now pushed immediately after each command.
Makes the owned-PTY execution from 0.8.2 the default (set LOKI_RUNNER_PTY=false to force Zellij). This removes the structural cause of the launch timeouts: launching no longer depends on an attached Zellij client.
First step of moving agent execution off Zellij name-puppeting onto Loki-owned PTYs (docs/architecture/agent-execution-platform.md). Ships node-pty in the runner (load-verified in the packaged build) but stays behind a flag so this release behaves exactly like 0.8.1 until you opt a project in.
The reliability release for the dispatch loop (Control → your local Zellij). Pairs with the web app's new Terminal tab, real Activity timeline, sidebar Light/Dark/Auto switch, and the dark-first Geist redesign deployed on loki.orangecat.ch.
Mostly a web UI + API release — deploys immediately on loki.orangecat.ch. Fleet Runner v0.8.0 picks up the improved remote switch_agent poller when you next update the desktop app; until then, cloud-queued switches still work on the existing runner.
Closes the last dogfood gap found while testing production Control against the local Zellij workspace: queued focus commands were claimed but could appear to land on the wrong tab without useful logs.
Completes the v0.7.7 Control-state fix by making the desktop app report the same installed tools the user's terminal can actually run.
Fixes the UI drift where Fleet Runner showed open workspaces but each project still said 'No live observation'. The pusher now sends the same rich runtime snapshot the server route already knew how to store.
Fixes the dogfood gap where the cloud UI looked connected but only `truthseeker` appeared and most remote actions were rejected by the desktop poller. This release makes the web app and phone UI a real controller for the local Zellij workspace.
Permanent fix for the .deb auto-update problem the user surfaced on 2026-06-07: they installed v0.7.0, received zero update notifications across 4 shipped releases (v0.7.1-v0.7.4), and didn't know they were stale. The auto-updater was downloading the new .debs but couldn't apply them — Linux dpkg requires sudo and Electron can't escalate. Now even when auto-apply fails, the user always sees what to do.
Architectural cleanup. The bundled renderer was an aspirational 'local-first' surface that never reached parity with /control. Deleting it (2,135 lines) puts Fleet Runner in the same category as Slack / Linear / Notion desktop: web UI + native integrations (tray, deep-link auth, IPC for Peek + auto-mint + local-dev-scan, auto-update).
Fixes a real bug where a revoked token would lock the user permanently offline — auto-mint's 'if existing token, bail' guard kept reusing the dead one. Now the runner deletes bad tokens on 401, and the next /control load mints a fresh one from the signed-in browser session.
Closes the biggest visibility gap in /control: you could see tab names and state chips but had to alt-tab into Zellij to see what an agent was actually saying. Peek brings the agent's view into Loki itself.
Emergency revert. v0.7.0 shipped the bundled renderer as the default boot target before it had parity with /control, which produced the 'YOUR MACHINES. YOUR AGENTS. / 0 projects / Sync error: Failed to fetch' screen. v0.7.4 later deletes the bundled renderer entirely; v0.7.1 was the safe rollback to v0.6 behavior in the meantime.
Major v0.6 → v0.7 cut. Several real features landed (autopilot, typed boundary, scheduler) but the Phase C 'make the bundled renderer the primary' change went out before parity work was done. v0.7.1 reverted that part; v0.7.4 deleted the bundled renderer for good.
The architectural foundation for everything that came after. Pre-v0.6, /control polled /api/control every 30s and the daemon-offline indicator was always lying about staleness. v0.6 inverts the dataflow: clients subscribe to an SSE stream, the bridge LISTENs on Postgres NOTIFY events, every row change fans out to connected clients within milliseconds.