Skip to content

Audit: socket activation as the default pattern for laptop services #1822

Open

Description

@dezren39

Direction (Drew, 2026-10-28)

Reviewing the #437 decision, Drew's ruling: systemd socket activation — systemd holds the port across service restarts so connections queue instead of being refused — 'should be what we do for everything', and asked why it isn't already the pattern across the board.

Verified current state (2026-10-28, flake at laptop ~/git/code)

Exactly ONE service does this today: OpenChamber (systemd.user.sockets.openchamber, ListenStream 127.0.0.1:3000, home/common/default.nix:999). A grep of the flake finds no other socket units, no LISTEN_FDS consumers, and no socket-proxyd anywhere. Everything else binds its own port and refuses connections while it restarts:

  • PM stack: pm-aggregator (:4870), pm-projector (:7801) — plain systemd services.
  • PM apps (Postgres, Kaneo, Grist, Pad) — Docker containers (compose today, oci-containers once feat(nixos): pm-apps — Kaneo, Grist, Pad, ready-for-agent as flake services (code#1752) #1762 lands); containers cannot inherit a systemd socket fd the same way.
  • OpenCode server (laptop :13420 / tailnet serve :4096) — plain user service.
  • OpenClaw gateway (:18789) — plain user service.
  • cloudflared, id-mail services — to be inventoried.

Honest constraint (why this is an audit, not a blanket flip)

Socket activation only works when the program can accept a socket handed to it by systemd (the LISTEN_FDS protocol — trivial shim for our own Node services, supported natively by many daemons) or when a proxy shim (systemd-socket-proxyd) in front is acceptable, which adds a hop and its own failure modes. Postgres and Docker containers are the known hard cases. The audit's job is to apply it everywhere it genuinely helps and record a reason where it doesn't — not to force it where it can't work.

Acceptance criteria

  • An inventory in this issue: every laptop service that listens on a port, with a per-service verdict — socket-activate (with the mechanism), or skip (with the concrete reason).
  • Implementation issues filed for each 'socket-activate' verdict (the four bin/ proxies already have theirs: bin/ proxies: systemd socket activation so restarts never refuse connections #1821).
  • Drew reviews the inventory before the implementation issues are queued — the container/Postgres cases in particular are his call if a proxy shim is proposed.

Activity

  1. developing-today commented on Oct 28, 2026

    @developing-today
    Contributor
  2. added
    kind/infrastructureInfrastructure / host-portability work
    source/humanFiled directly by a human
    priority/normalOrdinary priority (the default)
    effort/sSmall: expected under 2 hours of work
    area/infraInfrastructure, deployment, and operations
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 28, 2026
  3. dezren39 commented on Oct 28, 2026

    @dezren39
    MemberAuthor

    Intake review

    Fleet monitor intake pass (protocol §5), 2026-10-28.

  4. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Status — owner order 2026-10-28, stalled, re-queued 2026-10-31.

    Drew's 2026-10-28 ruling ("socket activation should be what we do for everything") produced this audit issue, but it was never queued — no task row existed.

    Remediation (2026-10-31): enqueued into repo-health-code-pilot as task 4721 (triage, priority 50, queued, attempts 0/3), verified by direct queue read; queue signal refreshed.

    Scope note recorded in the packet: the triage produces the per-service inventory on this issue only. Implementation issues from the verdicts are deliberately NOT queued — this issue's own acceptance criteria require Drew to review the inventory first (the container/Postgres cases in particular). The one pre-approved implementation is the bin/ proxies: #1821, separately queued as task 4719.

  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 1, 2026
  6. 5 remaining items

  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

    • Agent: repo-worker-code (OpenChamber scheduled lane)
    • Session / ID: OpenCode session ses_e5e6872657dffeS6wv0sU71Y30, run 2026-11-01
    • Found via: repo-health-code-pilot task 4721
    • Task: Captured a preliminary live listener/systemd snapshot, but the required full per-service mapping and flake cross-check are not complete.
    • Resume: Compare the live ss -lntup and systemctl --user list-sockets/services census against the laptop flake, then post every listener's socket-activation verdict and concrete mechanism/skip reason here. Do not file or queue implementation issues before Drew reviews the inventory.
    • Reason: The packet estimates a 120-minute triage; this scheduled lane cannot safely finish the full inventory within its remaining run window.
    • Links: Audit: socket activation as the default pattern for laptop services #1822
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — task 4721 triage inventory (lane C worker, 2026-11-01). Census taken live on the laptop (amd-6) two ways and cross-checked: (1) ss -lntup + systemctl --user list-sockets/list-units + docker ps; (2) flake grep of laptop ~/git/code. Per the task packet's HARD STOP, no implementation issues are filed from these verdicts — Drew reviews this inventory first (the issue's third acceptance checkbox; containers/Postgres especially). The four bin/ proxies remain covered by #1821 only.

    Flake cross-check

    • Exactly ONE socket unit in the flake: systemd.user.sockets.openchamber (home/common/default.nix:1012, ListenStream 127.0.0.1:3000) — matches the live openchamber.socket, the only user TCP socket unit.
    • Zero hits for socket-proxyd / socketProxyd / LISTEN_FDS anywhere in the flake. Every other verdict below starts from "binds its own port" — confirmed, no discrepancies between flake and live state on this point.
    • System level: docker.socket (unix) exists; sshd has only an sshd-unix-local unix socket — sshd binds :22 itself.

    Inventory — fleet services (the pattern's scope)

    Service Listener (live) Verdict Mechanism / reason
    OpenChamber 127.0.0.1:3000 DONE Already socket-activated (openchamber.socket); the pattern's reference case.
    pm-aggregator (bun, ours) 127.0.0.1:4870 + tailnet :4870 Socket-activate Our Node/Bun code: accept the systemd-passed fd (LISTEN_FDS shim, fd 3) instead of binding; socket unit holds :4870 across restarts.
    pm-projector (bun, ours) 127.0.0.1:7801 Socket-activate Same LISTEN_FDS shim as the aggregator.
    pm-dispatch (bun, ours) 127.0.0.1:4879 + tailnet :4879 Socket-activate Same shim. (Not in the 2026-10-28 issue snapshot — new since.)
    ready-for-agent (Node, ours) 0.0.0.0:6056 Socket-activate Same shim. (New since the issue snapshot.)
    mcpx (Go, ours) 127.0.0.1:34005 Socket-activate Go: build the listener from fd 3 when LISTEN_FDS is set (manual protocol or go-systemd/activation); our binary, no upstream blocker. (New since the issue snapshot.)
    opencode-route-proxy (bun, ours) 127.0.0.1:8443 Socket-activate Same LISTEN_FDS shim family. (New since the issue snapshot.)
    opencode server (Go, upstream) 127.0.0.1:13420 (+ :4747, :40369) Skip native; proxyd is Drew's call Upstream binary binds its own ports and ships no LISTEN_FDS support; only systemd-socket-proxyd in front would queue connections, at the cost of a hop in front of the fleet's main session server.
    bin/ proxy bridges (claude-code-proxy, codex-proxy, clef-proxy, commandcode-proxy, design-code-proxy, muse-proxy, antigravity-direct) dynamic localhost ports (several MainThread listeners) Already tracked Implementation issue #1821 already exists for this family — no new issue per the hard stop / dedupe.
    OpenClaw gateway (Node, third-party) 127.0.0.1:18789 + tailnet :18789, companions :18742–:18751 Skip Not our code; the gateway binds and manages a whole port family itself (gateway + per-instance listeners). Proxyd in front of one port wouldn't cover the family.
    pm-tunnel / cloudflared 127.0.0.1:20241 (metrics only) + ephemeral UDP Skip Outbound tunnel connector: it serves no inbound TCP listener of its own — the only TCP socket is localhost metrics, where a refused connection during restart costs nothing.
    Docker: pm-stack-postgres + pm-postgres 5432/tcp, container-internal only Skip — Drew's call Containers cannot inherit a systemd socket fd; nothing publishes 5432 on the host at all today, so there is no host listener to hold.
    Docker: kaneo / grist / pad 127.0.0.1:5173, :8484, 7777 (docker-proxy published) Skip — Drew's call Same container constraint: the host listener belongs to docker-proxy, not the app; systemd-socket-proxyd in front of the published port would add a hop and still not hold the container's own accept queue.

    Inventory — everything else listening (out of the pattern's scope, with reasons)

    Listener What it is Verdict
    tailscaled tailnet forwards (:443, :3000, :4096, :8443, :8484, :7777, :18742–:18746 on the tailnet IPs) Tailscale Serve forwards held by the tailscaled system daemon; backends are the services above Skip — system daemon owns these sockets; the fixable layer is the backend service, listed above.
    sshd :22 System OpenSSH Skip — system service, binds directly (only a unix sshd socket unit exists); socket-activating system sshd risks remote lockout and is outside the user-service pattern.
    systemd-resolved :53, CUPS :631, LLMNR :5355, mDNS/DHCP UDP OS plumbing Skip — resolved is socket-activated at system level by design; the rest are not services Drew's pattern targets.
    dbus / pipewire / pulseaudio / gcr-ssh-agent user unix sockets Desktop session sockets Already socket-activated (unix) — pattern holds; no TCP exposure.
    ActivityWatch aw-server 127.0.0.1:5600 Third-party time tracker Skip — not our code, localhost-only, restart-refusal impact is a missed heartbeat, not a refused user connection.
    RustDesk :21118 (+UDP), KRDP :3389 Third-party remote-desktop daemons Skip — they manage their own listener sets (incl. UDP rendezvous), not fleet services.
    Ad-hoc python3 servers 127.0.0.1:8898, :8899, :18777 (backlog 5) No systemd unit owns them — agent/session-spawned servers Skip — not services; flagged here because a listener with no unit is itself worth noticing. They die with their sessions.
    Antigravity / language_server / chromium / helium listeners (:41657, :35363, :19222, assorted ephemeral), bun :33681, node :3773 IDE/browser/session processes, incl. remote-debugging ports Skip — ephemeral session processes, not systemd services.
    127.0.0.1:3005 Unattributed: no owning process visible from the user session Discrepancy (below) — needs a root-level look before any verdict.

    Discrepancies vs the 2026-10-28 snapshot in this issue

    1. The PM apps are no longer "compose today" as a plan — all four containers are live now (kaneo + postgres up 9h, a second postgres up 39h, pad + grist up 2d), with the ports above.
    2. Four fleet listeners exist that the snapshot doesn't name: pm-dispatch :4879, ready-for-agent :6056, mcpx :34005, opencode-route-proxy :8443 — all ours, all socket-activate candidates in the table.
    3. cloudflared is inventoried: metrics-only TCP (:20241); nothing to activate.
    4. 127.0.0.1:3005 listens with no owner visible from the user session — I could not attribute it with user-level ss; naming it here rather than guessing.
    5. OpenClaw's footprint is a port family (:18742–:18751 companions alongside :18789), wider than the single gateway port the snapshot names.

    Bottom line for Drew's review

    Six of our own services are straightforward socket-activate candidates via a LISTEN_FDS shim (pm-aggregator, pm-projector, pm-dispatch, ready-for-agent, mcpx, opencode-route-proxy) — no proxyd needed. The genuinely judgment-call cases are exactly the ones the packet flagged: the Docker apps + Postgres (skip recommended; proxyd possible but weak) and the upstream opencode server (proxyd or nothing). No implementation issues filed — that waits on this review, per the hard stop.

  9. added
    user/reviewNeeds a user-look: Muse reviews first and handles what it can
    queue/queuedHas a queued task in the Supabase agent queue
    task/triageQueue packet kind: verify, reproduce, and route this item
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 1, 2026
  10. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: triaged (coverage pass)
    Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as triage at priority 40 (priority/normal). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
    Found via: coverage pass 2026-11-03

  11. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · triaged → escalated (Drew's review is the gate)

    • Agent: repo-worker-code (OpenChamber scheduled lane bf419306)
    • Session / ID: OpenCode session ses_e5e5e1353ceffeJh7tpGId1sUg · queue task 7750 (repo-health-code-pilot)
    • Checked: 2026-11-03T15:02Z against origin/main d3476bce7 (laptop clone fast-forwarded) and the live user systemd on amd-6
    • Verdict: still live, but the audit itself is already delivered — the per-service inventory is the 2026-11-01 comment above. What remains is acceptance criterion 3: your review before any implementation issues are queued. No implementation issues were filed.

    Delta since the 2026-11-01 inventory

    1. bin/ proxies: systemd socket activation so restarts never refuse connections #1821 landed two of the bin/ proxies. PR feat(proxies): socket-activate clef-proxy and codex-proxy (#1821) #4845 (merged 2026-11-03T11:16:48Z, merge b910a59) added systemd.user.sockets.clef-proxy (home/common/default.nix:509) and systemd.user.sockets.codex-proxy (:580), plus fd-3 / LISTEN_FDS support in both .mjs entrypoints. The flake now has three socket units total: openchamber + these two. The other five bin/ proxies (claude-code, commandcode, design-code, muse, antigravity-direct) still bind their own ports; bin/ proxies: systemd socket activation so restarts never refuse connections #1821 stays open for them and for the authorized post-merge host switch.
    2. Not yet activated on this host. The deployed home-manager generation is 2026-11-01T22:16Z, i.e. before that merge. systemctl --user status clef-proxy.socket codex-proxy.socket → both "could not be found", and sockets.target.wants/ still holds only openchamber.socket. Live census today: one TCP user socket (openchamber.socket 127.0.0.1:3000). That is bin/ proxies: systemd socket activation so restarts never refuse connections #1821's parked post-merge step, not a new blocker for this audit.
    3. New mechanism precedent — proxyd is in the flake now. 5ec2fc6dd (2026-11-02, fix(pm-stack): ready-for-agent binds 0.0.0.0:6056 while the rest of the stack binds loopback+tailnet - align bind scopes #1181) gave ready-for-agent a socket-activated tailnet entry: systemd.user.sockets.ready-for-agent-tailnet → systemd-socket-proxyd 127.0.0.1:6056 (nixos/services/pm-apps/default.nix:150-168). The inventory's line "zero hits for socket-proxyd anywhere in the flake" is therefore stale — the fleet already ships one proxyd instance, in front of a tailnet forward. The loopback listener itself is still direct-bound, so ready-for-agent's verdict is unchanged: LISTEN_FDS shim for the app, proxyd only for the tailnet door.
    4. Side finding from bin/ proxies: systemd socket activation so restarts never refuse connections #1821 is still open as proxy-port.test: parse-site count drifted from the tree — expects 8 sites, six proxies now have 7 #4846 (proxy-port.test site-count drift) — unrelated to this audit.

    Rows that need your ruling (unchanged from the inventory)

    Socket-activate via a LISTEN_FDS shim — our own code, no upstream blocker: pm-aggregator :4870, pm-projector :7801, pm-dispatch :4879, ready-for-agent :6056, mcpx :34005, opencode-route-proxy :8443.

    Your call specifically:

    • Upstream opencode server :13420 — socket-proxyd in front (one hop on the fleet's main session server) or nothing.
    • Docker / Postgres rows — kaneo :5173, grist :8484, pad :7777, postgres 5432: skip (recommended; the host listener belongs to docker-proxy and the container's accept queue can't be held) or proxyd anyway.

    Approve/deny per row and I file one implementation issue per approved row. Until then this issue stays open with nothing queued.

  12. added
    user/escalatedQueue or user escalation state
    and removed
    user/reviewNeeds a user-look: Muse reviews first and handles what it can
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area/infraInfrastructure, deployment, and operationseffort/sSmall: expected under 2 hours of workkind/infrastructureInfrastructure / host-portability workmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/humanFiled directly by a humantask/triageQueue packet kind: verify, reproduce, and route this itemuser/escalatedQueue or user escalation state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions