Skip to content

Run plain portal connect as a background daemon - #105

Draft
davegaeddert wants to merge 3 commits into
masterfrom
worktree-portal-connect-daemon
Draft

Run plain portal connect as a background daemon#105
davegaeddert wants to merge 3 commits into
masterfrom
worktree-portal-connect-daemon

Conversation

@davegaeddert

Copy link
Copy Markdown
Member

Stacked on #104.

Summary

  • plain portal connect <code> now spawns connect --foreground as a detached daemon (spawn, not fork — os.fork() crashes on macOS, which is why the original daemon was removed in 0.2), waits until the daemon reports Connected to remote. Session active., and returns. It's an ordinary blocking command again; no run_in_background needed for the local side.
  • plain portal disconnect is back. The daemon records its pid in the flock'd lock file, so liveness comes from the lock and disconnect never signals a stale or recycled pid.
  • Socket moved from .plain/portal/ to the system temp dir keyed by a project hash — under .plain/ a deep checkout path exceeded the AF_UNIX path limit (OSError: AF_UNIX path too long), which broke connect/exec entirely.
  • plain portal start flushes the portal code immediately, so it appears when stdout is a pipe/file (how an agent backgrounds heroku run). Previously it sat in the block buffer.
  • README fixed — it still described the pre-0.2 daemon (--foreground, disconnect, status, /tmp/plain-portal.sock) — and the skill no longer tells agents to background connect.

Why

In practice an agent had to background connect, and the session died whenever that shell was reaped ("the session dropped after the first exec"). Combined with the unflushed portal code, the skill rarely worked first try.

Test plan

  • Local loopback: start --read-onlyconnect returns with Session active.exec works → duplicate connect refused → disconnect stops daemon, remote exits, exec reports no session, second disconnect is a no-op
  • ./scripts/test plain-portal, ./scripts/type-check plain-portal

Not done here

The plain-dev Supervisor already implements this lock/pidfile/spawn/stop protocol; the daemon here mirrors its pattern but doesn't share code because plain-portal doesn't depend on plain-dev. Promoting Supervisor into core plain would let both use one implementation.

The database mode is no longer defaulted, so it is always visible in
the command itself. That lets an agent's permission classifier (or a
human reading a permission prompt) tell a read-only session from a
writable one without knowing the previous default. --writable is
renamed to --read-write to pair with --read-only.
connect was a foreground process holding the tunnel and a file lock for
its lifetime, so an agent had to background it and the session died
whenever that shell was reaped. It now spawns connect --foreground as a
detached process (spawn, not fork -- os.fork() crashes on macOS), waits
for the Unix socket to appear, and returns. plain portal disconnect
stops the daemon via a pid file.

Also:
- Move the socket to the system temp dir keyed by a project hash. Under
  .plain/ it exceeded the AF_UNIX path limit on deep checkouts.
- Flush the portal code from start immediately so it appears when
  stdout is a pipe or file.
- Fix the README, which still described the pre-0.2 daemon.
The daemon now writes its pid into the flock'd lock file instead of a
separate pid file the parent wrote. Liveness comes from the lock, so
disconnect never signals a stale or recycled pid. connect no longer
probes the lock or clears the socket itself -- the daemon does both,
and readiness is its own 'Session active' line in the log.
@davegaeddert
davegaeddert marked this pull request as draft August 31, 2026 02:42
Base automatically changed from worktree-portal-explicit-mode to master August 31, 2026 03:19
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