Skip to content

gpu-browser (2/2): npx @agent-socket/gpu-browser installs it under its own OS account and shares it - #2

Open
repalash wants to merge 35 commits into
claude/gpu-browserfrom
claude/gpu-browser-cli
Open

repalash wants to merge 35 commits into
claude/gpu-browserfrom
claude/gpu-browser-cli

Conversation

@repalash

Copy link
Copy Markdown
Collaborator

What this PR does

Adds npx @agent-socket/gpu-browser. On its first run it installs the GPU browser server from part 1 (blitzdotdev/agent-socket#1) under a dedicated OS account. Every run prints a link for the agent, and you answer the agent's requests in that same terminal.

Why

Part 1 only runs as a development run. Users need it isolated (it must not read their files) and installed without reading a README or running manual steps.

What gets installed, after one confirmation and one sudo prompt. Details and order are in gpu-browser/install/README.md:

  • The agent-socket-gpu-browser account. No login, no password, its own group.
    • macOS: created with dscl the way MacPorts creates its user. The login attributes macOS adds on its own are deleted, so the account stays out of Users & Groups and the login window. IsHidden is not used, because EDR tools flag it.
    • Linux: a system user, added to render/video so Chromium can open the GPU.
  • Downloads, run as that account:
    • Node.js 22, pinned by version and SHA-256 per platform;
    • playwright-core, installed with --ignore-scripts;
    • Chromium.
  • A root-owned copy in /opt/agent-socket-gpu-browser. The account can run it but not change it.
  • A self-test of the new copy, started exactly the way every run starts it. On macOS that's launchctl asuser <uid> sudo -u …. It checks that Chromium launches with its sandbox and reports the GPU. On Ubuntu 23.10+ it adds an AppArmor userns profile for this Chromium only. The copy is swapped in only if the test passes, so a failed update leaves the working install alone.
  • One sudo rule. It lets the installing user run exactly the start command without a password, and nothing else. I checked that on Linux: another command as the account is refused, root is refused, and the start command with an extra argument is refused too. The file is named zz-… because sudo applies the last matching rule. Testing showed that a user's own broader rule in /etc/sudoers.d/<user> otherwise wins and asks for a password. If that happens anyway, the CLI asks for the password once instead of failing.
  • Your home folder. If the account can open it, the CLI offers chmod 700, and uninstall offers to restore the old mode.

Running it:

  • The link lasts 60 minutes by default (--for changes that).
  • Approvals happen only in the terminal, and need a TTY: without one, every request is denied. You get a bell, and on macOS a notification. always saves the host to ~/.agent-socket/gpu-browser/allowed-hosts.txt.
  • Server output is untrusted. The CLI re-validates every event the server sends and strips control and bidi characters from its output.
  • Ctrl-C ends the link cleanly.
  • status, stop, install and uninstall. A root-owned uninstall.sh stays in /opt, so removal works even after the npm package is gone.
  • A README in every folder it creates: /opt/…, the account's home, ~/.agent-socket/ and ~/.agent-socket/gpu-browser/.

Base branch is part 1's branch, so this diff shows only part 2.

Checklist

  • Affects: gpu-browser/ (new bin/ and install/), CI (new path-filtered gpu-browser.yml), and docs.
  • Tests cover the new behavior:
    • gpu-browser/test/install-smoke.sh does a real install, start, a link used like an agent would (it checks the server runs as the account with Chromium's sandbox, a page, a screenshot, loopback blocked), stop with the link dead, then uninstall with nothing left. CI runs it on fresh ubuntu-latest and macos-latest runners, plus shellcheck and dash -n.
    • Locally on Linux, I ran it for real as a non-root user with password sudo: install, sudo-rule narrowness, an approval (always, then y for one session only), an update, and an uninstall that restores the home mode.
  • npm test: unchanged parts pass. The GPU browser unit tests (8) and harness 63 pass on this branch.
  • Needs a relay redeploy: no.
  • No secrets or API tokens in the diff.
  • READMEs, docs and CHANGELOG updated: root README section, SECURITY.md, PRIVACY.md section, CONTRIBUTING, CHANGELOG.

Not verified by me: the macOS path. That covers the dscl account, Chromium under launchctl asuser for an account that never logged in, the sudo rule's matching on macOS, and the real Metal GPU. The macOS CI job exercises all of it except the GPU, so its result is the first real answer. Publishing @agent-socket/gpu-browser to npm is a maintainer step.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53


Generated by Claude Code

…account and shares it

The CLI (bin/, runs as you) and the root installer (install/) for the
server from the previous commit.

First run: explains what it will do, asks once, one sudo prompt. The
installer creates the agent-socket-gpu-browser account (no login, no
password, own group; on macOS via dscl the way MacPorts makes its user,
with the implicit login attributes removed; on Linux a system user, in
render/video for the GPU), fetches Node.js (pinned by SHA-256),
playwright-core (no install scripts) and Chromium as that account, makes a
root-owned copy, self-tests it by starting Chromium exactly the way every
run starts it (adding an AppArmor userns profile on Ubuntu 23.10+), and
only then swaps it in and writes one sudo rule: the installing user may
start that server as that account without a password, nothing else. The
rule's file is zz-agent-socket-gpu-browser because sudo applies the last
matching rule; if a broader rule still wins, the CLI asks for the
password instead of failing.

Running: prints the link (60 min by default, --for), relays approvals to
the terminal (TTY only; `always` remembers the host in
~/.agent-socket/gpu-browser/allowed-hosts.txt), re-validates everything
the server says, and ends the link on Ctrl-C. status, stop, install,
uninstall (a root-owned uninstall.sh stays in /opt for when the package
is gone). Every folder it creates has a README.

Tested on Linux for real (a non-root user, real account, downloads, sudo
rule, sandboxed Chromium, approval, update, uninstall leaving nothing).
CI does the same on fresh Ubuntu and macOS runners, path-filtered, plus
shellcheck and dash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-11T19:43:05.572171Z 1ed14fd New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1e7820ccdd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpu-browser/install/uninstall.sh Resolved
Comment thread gpu-browser/install/install.sh Resolved
claude added 17 commits October 11, 2026 18:26
…d on Linux)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
…ppArmor glob

- install.sh: a failed or interrupted first install removes what it
  created (account, group, AppArmor profile, /opt dir); a failed update
  keeps the working install. An existing group of the account's name is
  never reused; uninstall removes only the account's own group.
- AppArmor profile attaches to $DIR/**/chrome; when the self-test still
  fails, both runs' output is shown.
- CLI: "minutes" in config.json must be a number >= 0; the note of the
  home folder's old mode is kept unless it was restored or you declined.
- install-smoke.sh shows the CLI's output when an agent call fails; CI
  prints AppArmor kernel messages on a Linux failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
…sandbox on/off/profiled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
Linux: the AppArmor profile over a dot directory and a system account
started through sudo. macOS: whether closing a context ends Chromium for
the runner user too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
…ly and through Playwright

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
…four ways

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
…ce the crash

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
…nchctl asuser)

In the launchd domain of an account that never logs in, Chromium crashed
(SIGSEGV) shortly after a browser context closed. Started with plain
sudo -u it doesn't, and it still renders on the GPU (CI: "ANGLE Metal
Renderer: Apple Paravirtual device"). The start command, the sudo rule
and the self-test are now the same on macOS and Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
…stics

Both install jobs pass now; the diagnostics steps were for finding the
two causes (launchctl asuser on macOS, an inherited XDG_CONFIG_HOME on
Linux) and are gone again, as is the no-cancel concurrency they needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7475095d6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpu-browser/install/install.sh Resolved
Comment thread gpu-browser/install/install.sh Resolved
…pt dir we didn't make

- A failure or signal in the middle of the swap moves what is in .old
  back, so the previous install keeps working (tested by failing an
  update right after its first move).
- /opt/agent-socket-gpu-browser that has neither install.env nor the
  new .created-by-agent-socket-gpu-browser marker is refused by install
  and left alone by uninstall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f386d2a14d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpu-browser/install/install.sh Resolved
Comment thread gpu-browser/install/install.sh Resolved
…or profile it added

- A machine-wide lock (/var/run/agent-socket-gpu-browser-install.lock,
  mkdir + pid) keeps two installs from sharing .staging/.new/.old; one
  left by a killed run is taken over. Tested: a second install started
  during the first is refused, the first completes, the lock is gone.
- The cleanup removes an AppArmor profile created by this run before the
  first-install-only part, so a failed update doesn't leave one behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 519dbdbb9a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpu-browser/install/install.sh Resolved
Comment thread gpu-browser/install/install.sh Resolved

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2273975559

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpu-browser/install/install.sh Resolved
Comment thread gpu-browser/install/install.sh Resolved
…keep special home mode bits

- /etc/sudoers.d/zz-agent-socket-gpu-browser or
  /etc/apparmor.d/agent-socket-gpu-browser without the installer's marker
  stops the install before anything is created (tested with a sudoers
  file of that name: refused, file unchanged).
- The home folder's old mode is recorded with its setgid/sticky bits
  (e.g. 2750), so uninstall can restore it exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
…t by a hard kill

- The lock is created as a symlink whose target is the installer's pid
  (atomic, never ownerless), so a second installer can't mistake a just-
  created lock for a stale one; only the owner removes it.
- A .old left by an update killed mid-swap (SIGKILL, power loss) is put
  back at the start of the next run instead of being deleted.

Tested: a live lock refuses; a stale one (dead pid) is taken over; an
install with node moved into .old is recovered and updates cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b4b9a930f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpu-browser/install/install.sh Resolved
…nning server

- The install directory and its marker come first, and the marker
  journals the group, account and AppArmor profile before each is made,
  so a first install killed hard (SIGKILL, power loss) is finished or
  undone by the next run, including a group left between groupadd and
  useradd (tested: reused, then removed when that run failed). The
  journal is emptied once an install finishes.
- The swap is refused while a server runs (its pid file), checked while
  holding the install lock; the server refuses to start while the lock is
  held (PR #1). Tested both ways on an install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fea5fd983a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpu-browser/install/install.sh Resolved
…t can prompt; safe uninstall

- An update keeps .old (and writes install.env aside) until its last
  step, so a failure after the swap still restores the working copy with
  its own install.env (tested by failing an update just before that step).
- Without the sudo rule, the start no longer passes -n, so sudo can ask
  for the password itself when the earlier sudo -v isn't cached.
- uninstall runs this package's own uninstall.sh (not whatever is in
  /opt/agent-socket-gpu-browser), and says there is nothing to remove
  when that directory has neither install.env nor the installer's marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ed14fd454

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpu-browser/install/install.sh Resolved
Comment thread gpu-browser/install/install.sh Resolved
…m runs; re-check a lock takeover

- A recorded pid (the install lock's, the server's pid file) blocks only
  while ps shows that same program (install.sh; server.mjs under the
  account's uid), so a reused pid no longer refuses installs or updates.
- After taking over a stale install lock, the installer waits a second
  and checks the lock still points at it, so two runs recovering the same
  stale lock can't both proceed.

Tested on an install: pid files pointing at unrelated live processes no
longer block an update, a start or an install; a real server still blocks
an update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CpTxf6FhMdiFuXFsbUam53
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

repalash commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Integration harness: 90-ws-rate-limit is red, and not because of this PR. Scenario 63 (the GPU browser) and every scenario before 90 pass. This PR doesn't touch the relay or that scenario.

Cause. The local Cloudflare simulator's rate limiter (miniflare/.../ratelimit.worker.js) counts in fixed windows aligned to the wall clock: epoch = floor(Date.now() / (period * 1000)), and it clears all counts when the epoch changes. The scenario fires 150 upgrades at a limit of 100 per 10 s. When that ~0.6 s burst straddles a 10 s boundary, each window sees fewer than 100, so nothing gets a 429. It has now happened twice on this PR (all 150 returned 101). It also passes most runs, including the previous commits here.

Proposed fix (harness only; I haven't pushed it here, so this PR doesn't widen): start the burst at the beginning of a window.

--- a/harness/scenarios/90-ws-rate-limit.mjs
+++ b/harness/scenarios/90-ws-rate-limit.mjs
@@
   const a = new Assert("90-ws-rate-limit")
+  // The local limiter counts in fixed windows aligned to the wall clock
+  // (floor(now / 10 s)); a burst straddling a boundary splits across two
+  // windows and may never reach the limit. Start it at a window's start.
+  await new Promise((r) => setTimeout(r, 10_000 - (Date.now() % 10_000) + 50))
   const results = await Promise.all(Array.from({ length: 150 }, upgrade))

I couldn't re-run the failed job: the API refused with 403 "Resource not accessible by integration". A manual re-run, or the next push, will run it again.


Generated by Claude Code

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.

2 participants