Skip to content

feat: bots and browsers on Linux and macOS - #95

Merged
httpsphl merged 12 commits into
mainfrom
feat/unix-runtime
Oct 1, 2026
Merged

httpsphl merged 12 commits into
mainfrom
feat/unix-runtime

Conversation

@httpsphl

@httpsphl httpsphl commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Second slice of Linux/macOS support (spec 18, item 8). Bots and their browsers run off Windows the way they do on Windows.

Changes

  • Process groups (spec 14.1): each bot and browser process leads a group of its own, and stopping it sends SIGKILL to the whole group, like TerminateJobObject. ProcessJob now has one interface on both systems (prepare(&mut Command), assign(&Child), terminate()), so runtime/pipe.rs and browser/launch.rs lose their cfg(windows) blocks.
  • Environment:
    • Bots get what the owner's login shell sets up ($SHELL -l -c, a marker, then env -0), cached for 20 s, with a fallback to the daemon's environment.
    • This is the Unix counterpart of CreateEnvironmentBlock: a daemon started by systemd or launchd with a bare PATH still finds ~/.local/bin and Homebrew.
  • Claude Code: after PATH, ~/.local/bin, ~/.claude/local, /opt/homebrew/bin and /usr/local/bin.
  • Owner name: the account's full name (GECOS) via getpwuid_r.
  • Browser: browser/program.rs finds Edge, Chrome or Chromium:
    • macOS: in /Applications and ~/Applications.
    • Linux: on the owner's PATH.
    • Windows: unchanged.

Still missing (spec 14.1)

When the daemon dies, a group does not die with it the way a Job Object does. claude exits on stdin EOF, but a browser stays. Slice 3 handles this: the systemd --user cgroup on Linux, a sweep at start on macOS.

Checks

  • Linux (Docker, Debian with chromium + chromium-sandbox, non-root user):
    • clippy -D warnings and the full suite, three runs in a row, all green.
    • The browser tests ran against the real Chromium.
    • New tests: the group kills a grandchild, the login shell gives a PATH, parsing env -0 output, claude found in ~/.local/bin.
  • Windows: fmt, clippy, full botloftd suite.
  • macOS: CI only.

Manual test with real Claude Code (not done yet)

  • Linux:
    1. Run cargo run -p botloftd -- serve with a dev BOTLOFT_HOME.
    2. Create a bot from the app in the browser (pnpm dev with ?live) and chat with it.
    3. Ask it to open a page and confirm the browser starts.
    4. Pause the bot and confirm no claude or chrome process is left (pgrep -a).
  • macOS: the same steps. Also check that claude is found when it is only in ~/.local/bin.

What CI on the real runners showed (fixed here)

  • macOS: Cmd shortcuts. The page runs no Cmd shortcut from the virtual key, so Cmd+A in the panel did nothing. Cmd+A, C, X, V, Z and Shift+Cmd+Z now go with their editing command (commands). Elsewhere nothing changes.
  • Ubuntu: Edge sandbox. The runner image ships Edge and Chrome with sandbox helpers that are not setuid. CI sets them as their packages would.
  • Ubuntu: Edge's first start. It took over 20 s on a fresh runner, and later starts under 1 s. The daemon now waits up to 30 s instead of 10, and CI starts Edge once before the tests.
  • Failure reason. A browser that closes or hangs at start now says why: its most telling stderr line goes into the error the bot and the owner see, never into the log.

embed-resource is a build dependency on Windows hosts only, so build.rs
must not name it elsewhere.
- With no scheduled task off Windows, finding one answers 'not installed'
  instead of failing, so saving the settings works.
- The owner name falls back to LOGNAME and the home folder name.
- file: URLs keep the leading slash of POSIX paths, so bots can open
  pages from their folders.
- The config test's TOML literal had one backslash too many; Windows
  paths hid it by merging the separators.
- The installed binary has no .exe off Windows, so the expected names are
  sorted like the listing.
- Archiving a queued message holds the bot's start, so a fast start can
  no longer take the message first.
First slice of Linux/macOS support (spec 18, item 8). The Ubuntu job
covered only core and store; it now runs clippy and the tests of the
default members, with macOS alongside. The app crates join when the app
runs there.
After PATH, look in ~/.local/bin (native installer), ~/.claude/local,
/opt/homebrew/bin and /usr/local/bin, for a profile that never added it
to PATH.
- A process group per bot and per browser takes the place of the Job
  Object: stopping kills the whole group. The job interface now prepares
  the command and takes the child, so the runtime and the browser have no
  Windows blocks left.
- Bots get the environment of the owner's login shell (read once per
  20 s), so they find what the owner's terminal finds even when the
  system starts the daemon with a bare PATH.
- The owner's full name comes from the account entry.
- The browser is the first Edge, Chrome or Chromium installed.

Spec 14.1 describes it, and what is still missing: the group does not
die with the daemon until slice 3.
The browser's stderr is read until it ends, and the line that best says
why it stopped (a sandbox or namespace failure, else the first fatal one)
goes into the error the bot and the owner see. Nothing of it is logged.
…untu runner

- On macOS the page runs no Cmd shortcut from the virtual key, so Cmd+A,
  C, X, V, Z and Shift+Cmd+Z the owner presses go with their editing
  command. The macOS runner showed it: Cmd+A did not select the field.
- The Ubuntu runner image ships Edge and Chrome with sandbox helpers that
  are not setuid root, so they refused to start; CI sets them the way
  their packages do.
… time

The browser's stderr is kept as the line that best explains a failure for
as long as it runs, so a browser that hangs at start says why too. The
tabs test prints the list when it is not the one expected.
Edge's first start on a new Linux machine (the Ubuntu runner) took over
20 s, past the 10 s the daemon waited; a start that works takes under a
second either way. CI starts Edge once before the tests instead of the
diagnostic step.
Edge is the rarer one on Linux, and on the Ubuntu runner it took seconds
to open the first tab of each new profile, past the 5 s the daemon waits,
where Chrome took under one. CI no longer starts Edge before the tests.
@httpsphl
httpsphl merged commit c804570 into main Oct 1, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant