Untether 0.36.0: Claude Code background agents report back
Untether 0.36.0 keeps Claude Code sessions open while background agents and processes run, so their results reach Telegram. Plus six breaking changes to check.
On this page
“I’ll send a new message when they finish”
That’s Claude Code talking, not Untether. It hands a job to a few background agents, or kicks off a long test run in the background, wraps up its reply and promises to report back. In a terminal it does exactly that. Through Untether, which is how I run Claude Code from my phone, up until this release, it couldn’t: the report-back happened in a session nobody was reading, and the next message I sent from my phone often landed in a brand-new session that had no idea the agents had ever existed.
Untether 0.36.0 came out on 8 October and fixes that properly. Claude sessions now stay open while background agents and background processes run, results arrive in Telegram as messages of their own, and a status block shows what’s still going. Getting there took 25 release candidates and 77 days. It also has six breaking changes, so if you already run Untether, check your permission_mode settings and any unattended crons before you upgrade (upgrade checklist).
What went wrong with background work before 0.36.0?
Untether stopped listening the moment Claude gave its first answer. Anything Claude’s background agents or processes did after that happened out of sight, Untether eventually killed the process, and my next message went to a fresh session with no memory of the work.
The mechanism is boring, which is usually how these things go. Untether runs Claude Code headless, talking stream-json in both directions. When a reply finished, Untether read the result event, decided the run was over and closed Claude’s input. Claude Code doesn’t see it that way. When I probed Claude Code 2.1.283 for issue #776, a finished background task made the CLI wake itself up and start a new turn, exactly like it does in a terminal, but background work only survived (and only woke Claude) while that input stayed open. Close it straight after the first answer and the background command was marked killed about five seconds later.
I went looking for the damage on one of my own servers on 27 September, running 0.35.5rc8 across 60 Claude spawns that day:
| What happened | Count |
|---|---|
| Wake turns that ran but never reached Telegram (across 6 sessions) | 15 |
| Sessions quarantined | 9 |
| Follow-ups that landed in a fresh session | 9 |
| Messages run twice | 7 |
A quarantined session is one Untether has given up on, so the next message starts fresh without any of its context. The double runs were the worst of it. The old session would quietly answer my message out of sight, Untether would resend the same message to a fresh session, and the reply I could actually see turned up anywhere from 32 seconds to 10 and a half minutes later, written by a Claude that didn’t know what its own agents had just done.
There was an older version of the same bug, too. Back in July I filed #646: Claude Code can send a subagent to the background without an explicit run_in_background flag, and Untether only counted background work when that flag was set. Those subagents were invisible to the watchdog, so it saw a finished run, killed the process 60 seconds later and quarantined a healthy session.
The first fix wasn’t the last one, either. Once sessions stayed open, a 30-minute background hold started stopping agents that were still busy working (#829). The fix for that one then cut off silent poll loops (the until done; do sleep 60; done kind) at 30 minutes, even when the agent had asked for a two-hour timeout (#872). Both turned up on my own hosts within a day of each other: a monitoring loop that reads Untether’s logs caught the first, and I spotted the second from a chat on another machine.
How do live sessions keep background agents and processes running?
Untether now keeps a Claude session open after its answer for as long as background work is running, and it reads Claude Code’s own task events for background agents, shell commands and monitors instead of guessing from tool calls.
Claude Code has more ways to keep working after it replies than most people realise. It can start a shell command with run_in_background and carry on while the command runs (background commands), and it runs subagents in the background by default (subagents). The Monitor tool watches something in the background and reacts when it changes. A self-paced /loop reschedules itself with ScheduleWakeup, and a hook set to asyncRewake runs in the background and can wake Claude when it flags something (background hooks). Untether’s whole job in 0.36.0 is making sure whatever they report back reaches my phone.
Claude Code has been heading the same way on its side. Its headless docs now say a claude -p run can stay open after its input closes to wait for background work Claude started, with a 10-minute idle cap by default (background tasks at exit). That’s great for a one-shot script. It doesn’t help a chat, where I want to reply halfway through and have the reply land in the same session with all its context, so Untether keeps the input open and manages the session’s lifetime itself.
Claude Code already reports most of it in its stream-json output. A task_started event arrives when a background agent or command begins, agents send task_progress as they work, task_updated and task_notification arrive when something finishes, and background_tasks_changed carries a snapshot of everything still running. Older Untether dropped those events on the floor. Now it tracks each task by its id and turns later turns into Telegram messages of their own, each with a header that says why it happened: 🔔 Background task finished, 📡 Monitor, ⏰ Scheduled wake-up or 🪝 Hook feedback (a background hook, like a commit review, reporting its findings). Approval buttons work inside those turns, and a follow-up I send goes into the open session instead of starting a new one.
A wake turn arriving as its own message after Claude’s answer. From a demo run on a 0.36.0 release candidate, 7 October.
A session can’t stay open forever, so there are rules for closing it:
- After 60 seconds idle with nothing running in the background.
- After a 30-minute background hold, counted from the last time a background agent did anything, and never shorter than a wait Claude declared up front (a background command’s timeout, up to 2 hours, or a pending wake-up, up to 1 hour).
- After 4 hours in total, however busy it is (
[watchdog] live_session_max_sraises that). - When you send
/cancelor/new, change a setting, or restart Untether, with a notice in the chat.
If you’d rather not have any of this, [watchdog] live_sessions = false turns it off, and the chat sessions guide covers the rest. Live sessions are Claude-only for now. Codex, OpenCode and Pi don’t get them.
The ⏳ background block
While Claude works, the progress message now carries a ⏳ background (N) block listing the background agents and processes that are running. Agents show elapsed time, tokens, tool calls and the step they’re on. Background shell commands show elapsed time only, because Claude Code reports when they start and finish and nothing in between.
Two background agents still working after Claude has answered. Same demo run.
One detail I’d rather be upfront about: the token figure for an agent includes cached and system-prompt tokens, so even a trivial Haiku subagent showed around 38k in testing. It’s labelled “tokens” on purpose. It isn’t a cost.
After Claude answers, the block moves into one silent status message that gets edited in place until the tasks finish or the session closes, and /ping shows how many are still running. That matters more than it sounds. In the first live-session build, one background agent finishing could push two separate messages 12 seconds apart, both saying roughly the same thing (#785). Short acknowledgements now fold under their task’s row as ↳ lines, so a busy orchestrator with five agents doesn’t buzz my phone five times for nothing. Three settings control it: [progress] show_background_tasks, background_tasks_max_rows and consolidate_wake_turns. The cost line on the final reply also says when it includes background agents’ spend (incl. 2 bg agents in the screenshot), so a cheap-looking answer that kicked off two agents doesn’t hide what they cost.
What happens when a background agent needs permission?
It gets its own Telegram message with ✅ Approve and ❌ Deny buttons, re-sent after 10 minutes and then every 30 minutes until someone answers, and removed once they do.
This one came straight out of the release candidates. On 4 October, on my staging bot, an orchestrator session launched five background planning subagents and ended its turn. Within nine seconds one of the subagents had asked to run a Bash command. Untether received the request, built the approval and rendered no buttons at all, because the parent’s turn was already over. So the subagent waited, for 1 hour and 43 minutes, until the request was cancelled (#929). The request itself was a false alarm, which stings a bit: my repo’s own release-guard hook had matched the text of a restart command sitting inside a grep pattern and asked for approval.
Two smaller fixes sit alongside it. The “waiting for you” reminder now says what it’s waiting for (a question, a plan approval, or approval for a named tool), replaces the previous reminder instead of stacking another one, and disappears once you answer (#919). And if a live session gets killed from outside Untether, by the OOM killer or a stray kill, the chat now gets one notice naming the background tasks that stopped, instead of silence (#1001).
Can I talk to Claude while it’s still working?
Yes, with Claude. By default a message sent mid-run still waits for the current turn to finish. Set followup_mode to steer and it goes straight into the live session instead: Untether replies ↪️ Steered into the current run., and mid-tool the message folds into the running turn, otherwise it runs as its own follow-up turn (#775). /steer <text> and /queue <text> override it once, and /config has a new Follow-up page for setting the default per chat or topic (steering guide). Files, forwards, commands and a pending question always queue.
Steering a run that’s already going. The reply that came back included the Python version.
The status line also ends with how full Claude’s context window is now, like done · claude · 1m 36s · step 10 · 62% ctx. When Claude compacts, the progress message shows 🗜️ Compacting context… and then something like 🗜️ Context compacted · 182k → 41k tokens (auto), and compaction counts as an expected wait rather than a stall (#819). Before this, a long compaction just looked like Claude had gone quiet, which on a phone is indistinguishable from Claude having died.
The rest of 0.36.0: permissions that ask, and crons nobody’s watching
The breaking changes are mostly about permissions meaning what they say, and I’m not proud of how one of them was found.
In August I worked out that Untether’s prompting permission modes didn’t prompt (#749). default, manual and acceptEdits all behaved like bypassPermissions, because Untether sent --allowedTools "Bash,Read,Edit,Write" on every spawn and then approved most of whatever else reached it. The probe that showed it was two rows in a table. With default and no allowlist, Claude asked before running a touch command, and a deny stopped it. With default plus Untether’s allowlist, nothing asked, and the command ran anyway. Plan mode was never affected, which is exactly why I didn’t notice sooner. In 0.36.0 that allowlist isn’t sent in prompting modes, so anything the mode doesn’t cover becomes a Telegram approval.
The other breaking changes, in short:
- Untether’s own
automode is now calledplan-auto, andautomeans Claude Code’s own auto mode, passed through unchanged (#741). - Cron and webhook runs no longer wait on a button nobody can tap. An approval in an unattended run is denied at once, and the final message lists what was denied, like
🔒 unattended (cron:<id>) · denied Write ×2 — nobody to approve(#835). - With Loop mode off, Claude can’t schedule its own recurring tasks any more (they ran uncapped and came back on every resume). With Loop mode on, Untether runs the schedule itself, with its caps (#925).
- Codex safe mode is really read-only now.
codex exechad been ignoring the approval flag Untether passed, so Safe ran like full auto (#830). extra_argscan’t carry flags that bypass approvals or the sandbox, in any spelling (#209).
One security fix deserves a mention even though it isn’t breaking: the default file deny globs, like **/*.pem, didn’t match at the project root, so /file get key.pem would have delivered a root-level key (#831). They match at any depth now, and a symlink inside the project can’t route around them either (#390).
Faster, and cost figures you can trust
untether.toml used to be parsed on every read. A single Claude message parsed it 15 or more times, and each parse blocked the event loop for somewhere between 10 and 47 milliseconds. It’s now parsed once per edit, and edits still apply on the next read (#506).
The live-session work also turned up a bug in how Untether recorded each run’s cost. Claude Code reports total_cost_usd as a running total for the whole session, and Untether was recording that as the cost of each run, so every resumed run counted the session’s history again in the footer, the budgets and /stats (#778). Once one session could span many turns, that would have got silly fast, so per-run cost is now the run’s own spend and your per-run and daily figures may drop after upgrading. The Stop at limit budget setting finally works as well: once today’s total hits max_cost_per_day, new runs are refused until midnight, with a one-off Run anyway button. A reply that’s already running is never cut off, so the day’s total can land a little over the limit (#896), and the daily total survives a restart (#898).
What happened to Gemini CLI and Amp?
Both are deprecated as of 0.36.0, and for Gemini CLI the decision was really Google’s. On 19 May, Google announced on its developer blog that it was moving its terminal tool over to a new one, Antigravity CLI, and that from 18 June 2026 Gemini CLI would stop serving requests for Google AI Pro and Ultra subscribers and for anyone using it free through Gemini Code Assist for individuals. Enterprise licences and paid API keys still work. So if you ran Untether’s Gemini engine on a personal Google account, it stopped working on 18 June, whatever Untether did.
Untether keeps both engines included and selectable, marked with ⚠️ in /config, but they get no fixes, they’re out of the test matrix and they may be removed in a later release, though none is scheduled (#720). That leaves four supported engines: Claude Code, Codex, OpenCode (1.x) and Pi. Antigravity CLI, Google’s replacement, is planned as its own Untether engine for 0.36.1.
0.36.0 by the numbers
This was meant to be 0.35.5. It ran as 0.35.5rc1 through rc20, picked up enough breaking changes along the way that a patch number would have been dishonest, and got renumbered to 0.36.0 for another five release candidates (#947).
| 0.36.0 | |
|---|---|
| Release candidates | 25 (0.35.5rc1–rc20, then 0.36.0rc1–rc5) |
| Days since 0.35.4 | 77 (23 July to 8 October) |
| Changelog entries | 6 breaking, 23 changes, 64 fixes |
| Issues and PRs linked from the changelog | 201 |
| Files touched by the release PR | 410 |
Release candidates went onto my own machines first, and the monitoring loops that read Untether’s logs filed several of the issues above (#785, #829 and #1001 among them). That’s the same dogfooding loop I wrote about in Dogfooding found 22 bugs my 1,548 tests missed, just with more machines and a lot more background agents.
How do I upgrade without breaking my setup?
Most setups need a couple of config edits and a restart, which I’d budget 10 to 20 minutes for on a typical config. The upgrade notes go through each change in more detail, but this is the order I’d do it in:
- Search your config (
~/.untether/untether.tomlby default) forpermission_mode = "auto". If you want the old behaviour (plan mode with automatic plan approval), change it to"plan-auto". Chat settings made with/planmodeor/configare migrated for you; the config file isn’t. - Give every Claude cron that should act on its own an explicit
permission_mode(plan-auto,autoorbypassPermissions). A cron without one inherits the chat’s mode, and if that’s plan or a prompting mode it now ends with a plan or a report instead of doing the work. - Move any Codex chat or cron on Safe that needs to edit files, build or run tests to Full auto.
- Remove approval or sandbox bypass flags from
extra_args(for Claude, things like--dangerously-skip-permissionsor--allowedTools; for Codex,--yoloor--sandbox danger-full-access) and use the named settings instead (allowed_tools,permission_mode). The security guide has the full list. Untether names the flag and refuses to load the config, or switches that engine off if it isn’t your default. - If you deliver
.env.examplefiles through Untether, check yourdeny_globs, because**/.env.*now matches at the project root. - Upgrade with
uv tool upgrade untether(orpipx upgrade untether), restart the service and checkuntether --versionsays 0.36.0. The startup message lists any crons the permission changes affect. Then watch the next unattended cron that fires: if its final message is a🔒 unattendedlist of denied tools instead of the work, it still needs an explicitpermission_mode(step 2). - If the startup message lists crons you can’t sort out, or something misbehaves, go back to 0.35.4, but clean up first: 0.35.4 rejects any setting it doesn’t know, and one leftover key can stop it starting or switch off every cron. Delete the new keys you set (things like
live_sessions,followup_mode,own_scheduleor a cron’smodel), changepermission_mode = "plan-auto"back to"auto", reinstall withuv tool install --force untether==0.35.4, then send/planmode autoin any chat that used it. The rollback notes have the full table of new keys.
Next week I’ll write up the other half of what live sessions made possible: Claude Code loops and crons that keep working while I’m away from the desk. If you want to try 0.36.0 before then, uv tool install untether gets you started, the Untether page has the setup, and the help centre covers live sessions and steering in detail. Untether also has a home of its own now, at untether.cc.
Frequently asked questions
Do background agents work with Codex or OpenCode in Untether?
/steer are Claude Code only in 0.36.0, because they rely on Claude Code's live stream-json session. Codex, OpenCode and Pi run each message the usual way, with no live session, and a /steer sent to them falls back to queueing with a notice. How long does Untether keep a Claude session open?
[watchdog] live_session_max_s, and /cancel or /new closes an idle session straight away. Do I have to change my config to upgrade to 0.36.0?
permission_mode = "auto" to "plan-auto" if you want the old behaviour, give unattended Claude crons an explicit permission_mode, and remove bypass flags from extra_args. Chat settings migrate themselves. The help centre's upgrade notes cover each change in more detail. Can I roll back to 0.35.4?
live_sessions, followup_mode or own_schedule) and change plan-auto back to auto in untether.toml, then reinstall with uv tool install --force untether==0.35.4 (or the pipx equivalent) and send /planmode auto in chats that used it. The rollback notes in the help centre list every new key.