Skip to content

AgentOS 2026.9.4

Choose a tag to compare

@github-actions github-actions released this 04 Sep 01:34
· 49 commits to main since this release
2271955

A contributor-fix release. Seventeen community PRs, no new surface — this one is
about the places AgentOS was quietly wrong rather than anything new it can do.

agentos config set stops lying

Two separate failures met in the same command. skills.config.<skill>.<key> — a
documented, published example — could never be written: _set_key only
overwrote keys already present in to_toml_dict(), and an empty skills.config
is omitted there for rollback compatibility, so the map the command needed to
write into did not exist to be found. Missing intermediates are now created
under skills.config only, with unknown keys outside that map still rejected.

Worse, the no---config path printed a fabricated AGENTOS_GATEWAY_ export and
exited 0 for keys that have no environment binding at all — gateway.port and
every skills.config.* key among them. A user followed a confident hint, set an
environment variable nothing reads, and got a success code for it. Keys are now
validated against the model before any hint is printed. (#834)

Readers that survive a bad line

load_entries is the shared reader for cost-savings reports, session export and
pipeline replay, and the decisions JSONL it reads is append-only, written once
per turn. A SIGKILL mid-turn, an OOM or a full disk leaves a truncated line —
and every downstream report died on it together. The realistic corruption turns
out not to be a broken string but a wrong-shape payload, which surfaces as
ValueError/TypeError from _filter_payload rather than JSONDecodeError,
so all of them are caught now. Skips are counted and logged — one debug event
per line, one warning with the totals — so a partial report announces itself
instead of quietly under-reporting. This is the tolerance
decision_log_aggregate.parse_log_line already had; the two readers of the same
file finally agree on what is fatal. (#812)

Runtime correctness

An MCP client disconnecting used to take another client's tool with it: when two
servers registered the same name, disconnect unregistered it unconditionally and
the surviving client's tool vanished. Ownership is now tracked per handler, and
a still-active client's handler is restored instead of dropped (#801).

background_process caps retained output at 1,000,000 characters, evicting
older chunks so the recent tail is what survives — and keeps draining past the
cap, so a noisy subprocess cannot block on a full pipe (#803).

Provider credit exhaustion is no longer mistaken for a transient fault. OpenAI's
insufficient_quota arrives with HTTP 429 and read as RATE_LIMITED, tripping
the circuit breaker for a billing problem no cooldown can heal; Anthropic's
billing_error arrives with HTTP 402 and read as UNKNOWN, offering no
recovery hint at all. Both now classify as INSUFFICIENT_CREDITS (#777).

CLI JSON survives a non-UTF-8 terminal: UTF-8 bytes go to the binary buffer when
there is one, and the text fallback uses backslashreplace, so an em dash
becomes a round-trippable escape rather than a ? (#764). Memory-write refresh
callbacks now reach the running turn — svc._turn_runner_ref was never
populated, so refresh_memory_snapshot had nothing to call (#761). apply_patch
records UpdateFile in workspace_file_writes, so a patch that edits an
existing file reaches auto-publish, and standard @@ -a,b +c,d @@ hunk headers
parse (#753). A bare .dev version parses correctly, so development installs
stop comparing equal to their own final release and start seeing update notices
(#740). Email is marked seen after conversion, so a mid-conversion failure
leaves the message eligible for the next poll (#719). The Robinhood chain skill
handles a plain-string RPC error payload (#815), and gmgn-wallet-score prints
usage and exits 2 instead of raising IndexError on a short argv (#819).
Frontend line endings are normalised with text=auto — deliberately not a
blanket eol=lf, which would have marked PNG, webp and woff2 assets as text and
corrupted them — with Prettier set to endOfLine: "auto" (#825).

Security

A single soft hyphen defeated the injection guard. Invisible Unicode — soft
hyphen, word joiner, zero-width space, bidi isolators — placed between two words
split the intent-phrase regexes, so a payload passed clean in both report and
enforce mode. Invisible codepoints are normalised to a space before matching,
while invisible_char is still matched against the original text, so the
smuggling technique is reported rather than erased along with the evidence
(#690).

Per-IP rate limiting now covers the Control UI API subtree.
RateLimitMiddleware._is_ui_path() exempted the entire Control UI prefix,
including everything under {base_path}/api/* — so /control/api/sessions,
/control/api/chat and /control/api/config accepted unlimited unauthenticated
requests. It mirrors the check AuthMiddleware._is_ui_path() already had (#748).

send_file checks size before reading rather than after. Every adapter opened
the file first, so a large attachment meant memory exhaustion — the email
adapter base64-expands the whole payload in memory — or a long upload ending in
an API rejection. Each service's real ceiling is now enforced up front: Discord
10 MB, Telegram 50 MB, email 25 MB (#683). Search results carry their provider
origin, so backend-returned text is attributable to the guard that inspects it
(#688). And the Robinhood chain skill rejects a non-http(s) --rpc-url, which
had let a file:// URL turn an RPC call into a local file read (#816).

Install

curl -fsSL https://raw.githubusercontent.com/use-agent-os/agent-os/main/install.sh | bash

or uv tool install --python 3.12 "use-agent-os[recommended]==2026.9.4".

Contributors

Thanks to @tyler-james-bridges, @bukeeastrey, @Preciousuche, @fcfsprojects,
@BunnyTeddy, @hallucinates, @s97472091-pixel, @haririya, @tejajakarulloh,
@Tiktokaiagent and @kyveni — every fix in this release came from a community PR.

Full changelog: https://github.com/use-agent-os/agent-os/blob/v2026.9.4/CHANGELOG.md