Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ new version heading in the same commit.

## [Unreleased]

## [0.227.1] — 2026-07-17
### Fixed
- **`agent-browser` daemon leak — sessions now clean up their own browser (root-cause fix, not a GC).**
The `agent-browser` skill starts a persistent headless-Chrome daemon that **double-forks (`setsid`) out
of the session's tmux process group**, so `tmux kill-session` at teardown never reached it and it
survived — burning CPU (its `swiftshader-webgl` software renderer spins helpers at ~100%) and RAM — for
**days**, until reboot or OOM. On the instawp box this had accumulated 6 orphaned daemons (one agent
leaked 4) driving load average to ~25; killing them dropped it to ~1. Two changes make each session own
its browser lifecycle:
- `src/terminal.ts` (`sessionEnv`) now exports **`AGENT_BROWSER_NAMESPACE=aos-<session-id>`** (isolates
each session's daemon + socket + saved state) and **`AGENT_BROWSER_IDLE_TIMEOUT_MS`** (default 5 min,
operator-overridable — the vendor's self-shutdown, kept only as the last-resort net for the one exit a
trap can't catch: an un-trappable SIGKILL/OOM).
- `terminal/claude-launch.sh` adds an **exit trap** (`EXIT`/`HUP`/`TERM`/`INT`) that runs
`agent-browser close --all` — the vendor's clean shutdown — so the session tears its browser down on
**any trappable exit, including the SIGHUP `tmux kill-session` sends**. Scoped to the session's
namespace, so it can never touch another live session's browser. The launcher already stays claude's
parent (never `exec`s it), so the trap is live for interactive, unattended, and resident lanes.
Upstream-acknowledged gap (vercel-labs/agent-browser #885/#1334/#1371/#1401): the daemon has idle
self-shutdown but nothing cleaned up after abnormal termination — now agent-os does, at the source.

## [0.227.0] — 2026-07-17
### Added
- **Slack chat IDs now auto-link from a member's email.** When a notification (task assignment, approval,
Expand All @@ -25,6 +46,7 @@ new version heading in the same commit.
why and **scroll-jumps** to the relevant section below; the card hides once all four are done. This is
the manual fallback for what auto-link can't cover — chiefly **Discord**, which has no email to resolve
from — nudging members to link it themselves so task/approval DMs reach them.
## [0.226.0] — 2026-07-16
### Added
- **Distraction-free terminal + "Pop out" to its own tab.** The individual terminal view gets two new
affordances in its top-right toolbar. **Focus** (⤢) lifts the pane to a full-viewport overlay (`fixed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agent-os",
"version": "0.227.0",
"version": "0.227.1",
"description": "A generic, governed operating system for running autonomous agents safely across brands. Ships with a local web console.",
"license": "MIT",
"type": "commonjs",
Expand Down
13 changes: 13 additions & 0 deletions src/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,19 @@ export class TerminalManager {
AGENT: agent,
TASK_B64: Buffer.from(task, 'utf8').toString('base64'),
AOS_SECRET: secret,
// The `agent-browser` skill starts a persistent headless-Chrome daemon that double-forks to init and
// so escapes the session's tmux process group — `tmux kill-session` at teardown can't reach it, and
// it (plus its swiftshader Chrome, which burns CPU) survives for days until reboot/OOM. Two env knobs
// let the SESSION clean up after ITSELF (root cause) instead of a process-scanning GC:
// • NAMESPACE per session isolates its daemon + socket + saved state, so the launcher's exit trap
// (`agent-browser close --all`, terminal/claude-launch.sh) shuts down THIS session's browser on
// any trappable exit — including the SIGHUP `tmux kill-session` sends — without touching another
// live session's browser.
// • IDLE_TIMEOUT is the last-resort net for the ONE exit the trap can't catch: an un-trappable
// SIGKILL (OOM). The daemon self-exits after this many ms with no commands. Operator-overridable;
// 5 min is long enough not to interrupt a multi-step browse (LLM think-time between commands).
AGENT_BROWSER_NAMESPACE: `aos-${id}`,
AGENT_BROWSER_IDLE_TIMEOUT_MS: process.env.AGENT_BROWSER_IDLE_TIMEOUT_MS || '300000',
};
// Under the launcher, the systemd-run scope starts with a minimal PATH; seed it with the dir that
// holds this app's node (claude is usually installed alongside it) plus the standard bins. Flag
Expand Down
16 changes: 16 additions & 0 deletions terminal/claude-launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ red() { printf '\033[31m%s\033[0m\n' "$1"; }

cd "$AGENT_DIR" 2>/dev/null || { red "agent folder not found: $AGENT_DIR"; exec bash; }

# Browser self-cleanup (root-cause fix for the agent-browser daemon leak). The `agent-browser` skill
# starts a persistent headless-Chrome daemon that double-forks (setsid) OUT of this pane's process
# group, so `tmux kill-session` at teardown can't reach it and it survives — burning CPU (swiftshader)
# + RAM — until reboot/OOM. Fix: this session OWNS its browser and shuts it down when the launcher
# exits. `close --all` is scoped to this session's AGENT_BROWSER_NAMESPACE (set per-session by the
# server; derived from SESSION here so it's right even on the RESUME path, whose recovered env may
# predate the var), so it can never touch another live session's browser. The signal traps make it run
# on the SIGHUP/SIGTERM `tmux kill-session` sends (both trappable) as well as a clean exit — only an
# un-trappable SIGKILL (OOM) slips past, and AGENT_BROWSER_IDLE_TIMEOUT_MS is the net for that.
export AGENT_BROWSER_NAMESPACE="${AGENT_BROWSER_NAMESPACE:-aos-${SESSION:-}}"
aos_browser_cleanup() { command -v agent-browser >/dev/null 2>&1 && agent-browser close --all >/dev/null 2>&1; return 0; }
trap aos_browser_cleanup EXIT
trap 'exit 129' HUP
trap 'exit 143' TERM
trap 'exit 130' INT

# Wire the gate as a project-local PreToolUse hook. claude inherits AOS_URL/SESSION/AGENT from
# this shell's env, so the hook can reach the gateway and tag the right session. We regenerate
# the settings each launch so the hook path is always correct (and portable across machines).
Expand Down
Loading