Skip to content

Let the connector run on macOS - #812

Merged
robzolkos merged 6 commits into
mainfrom
connect-on-mac
Oct 1, 2026
Merged

robzolkos merged 6 commits into
mainfrom
connect-on-mac

Conversation

@robzolkos

@robzolkos robzolkos commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

basecamp connect refused to run on a Mac: "The connector runs on Linux only, not darwin: … Linux is the only platform that seals the descriptors a process inherits". Most of us are on Macs, so most people couldn't connect an agent at all.

That reason turned out not to be true. When the connector hands a worker its one-time token, it passes it on an inherited file descriptor. The CLI's first job at startup is to mark every inherited descriptor close-on-exec, so nothing it starts can pick the token up. It only did that on Linux. macOS can do the same: list /dev/fd and mark each one, exactly as the Linux fallback for older kernels already does with /proc/self/fd. I checked this on a Mac first. Without the marking, a child process could see the descriptor; with it, it couldn't.

What changes

  • At startup on macOS, the CLI marks inherited descriptors close-on-exec by listing /dev/fd. If that can't be read, it refuses to start rather than guess which descriptors are open.
  • The token reader is built for macOS as well as Linux. The existing test that keeps sealing and token-reading paired on every platform we release for still holds them together.
  • basecamp connect runs on macOS. On other platforms it now says "Linux and macOS only".
  • The background service (hidden for now) stays Linux-only, since it writes a systemd unit. On a Mac it now says so in plain words.

Found by running the connector's tests on a real Mac

CI only runs the Go tests on Linux, so this was the first time they'd run on macOS. Three things came up, and the first two are real behaviour differences:

  • macOS won't signal a process group whose only members are zombies. That's a group of processes that have exited but haven't been cleaned up yet. Linux delivers the signal; macOS refuses with "operation not permitted". The connector took that refusal to mean the group might still be running, so it couldn't confirm a finished worker had gone. Now, when the system's own process list shows nothing in the group is running, it's treated as gone, the same as on Linux. A group that's still running but can't be signalled (another user's) still lists its members, and stays refused.
  • macOS only names a socket's peer process while it's connected. If a worker's MCP server closed before it read its token, the connector recorded an untrusted connection and gave up on that socket. Now it's "undelivered", as on Linux, and the server's next start gets the token. A different user is still refused.
  • A test's fake worker crashed on macOS before the test could run, because it blocked with an empty select. It sleeps now.

Tried on a Mac

  • The connector's test suites, built for macOS and run on an Apple Silicon Mac: every connector package passes. One timing-sensitive token test failed once in its first ten runs, then passed in seventy runs after; I didn't catch why that first one failed.
  • Five general CLI tests (terminal detection, delete confirmations, bare basecamp, the setup wizard) fail on macOS on main too. They're unrelated to the connector and not touched here.
  • End to end, against a local Basecamp through an SSH tunnel: auth agent connect, connect setup and connect doctor all passed. The connector picked up a mention, boosted 👀, replied and completed the task. The worker was the e2e stand-in, because over SSH macOS keeps the Keychain locked and Claude Code reports itself logged out.

Summary by cubic

Lets basecamp connect run on macOS. Previously it refused to start on a Mac because the CLI only sealed inherited descriptors on Linux; macOS can seal its own by listing /dev/fd, so the token handover now works there. macOS always mounts /dev/fd, so if it can't be opened the CLI fails closed rather than guessing. The background service stays Linux-only since it writes a systemd unit, and now says so on a Mac.

Fixes found by running the connector's tests on a real Mac:

  • macOS refuses to signal a process group whose only members are zombies; the connector now treats such a group as gone when the kernel's own process list shows nothing running. Linux behavior is unchanged.
  • A worker's MCP server that closed before its token was read is now recorded as "undelivered" instead of "refused", so its next start gets the token.
  • The token's recipient is now named from the trust check before the token is written, so a server that reads its token and closes at once still resolves to the right process instead of a reused pid.
  • A test's fake worker that blocked on an empty select (a runtime deadlock on macOS) now sleeps instead.

Written for commit 352932e. Summary will update on new commits.

Review in cubic

The connector was Linux-only because the CLI only sealed inherited descriptors
there, and the task token arrives on one. macOS can seal them too: list /dev/fd
and mark each close-on-exec, as Linux's fallback already does with
/proc/self/fd. The token reader is built for both, and the platform check
allows macOS. The background service stays Linux-only, since it writes a
systemd unit.

Also, found running the connector's tests on a Mac:
- macOS refuses any signal to a process group whose only members are zombies,
  where Linux delivers it. A finished worker's group read as "probably still
  there" and couldn't be confirmed gone. Such a group is now the absent group
  it is everywhere else, when the kernel's own listing shows nothing running.
- The claude driver's "deaf" test worker blocked on an empty select, which
  the runtime ends as a deadlock on macOS before the pipe fills. It sleeps.
…fused

macOS answers LOCAL_PEERPID only while the peer is connected (it still names
the user). A worker's MCP server that closed before its token was read was
recorded as an untrusted connection and spent the socket; it is now
undelivered, as on Linux, and its next start gets the token.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 00:34
@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) labels Oct 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The Darwin fallback can leave high-numbered inherited descriptors unsealed, and shipped skills still reject macOS.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity · 1 Low severity

Open (3)
What changed in this PR

Enables the connector on macOS by sealing inherited descriptors and handling Darwin-specific process and socket behavior.

Changes:

  • Adds macOS descriptor sealing and token handoff support.
  • Handles closed peers and zombie-only process groups on macOS.
  • Keeps systemd service management Linux-only and updates platform tests.

[!TIP]
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

File Description
internal/​connector/​tokensocket.go Handles closed same-user peers as undelivered.
internal/​connector/​tokensocket_test.go Tests closed-peer handoffs.
internal/​connector/​tokensocket_darwin.go Recognizes disconnected macOS peers.
internal/​connector/​driver/​worker_unix.go Handles zombie-only process groups.
internal/​connector/​driver/​claude/​claude_test.go Avoids deadlocking fake workers.
internal/​connector/​admission/​policy.go Updates platform documentation.
internal/​commands/​mcp_token_sealed.go Enables token reading on macOS.
internal/​commands/​mcp_token_other.go Updates unsupported-platform handling.
internal/​commands/​connect.go Documents macOS support.
internal/​commands/​connect_setup_guided_test.go Moves unsupported-platform coverage to Windows.
internal/​commands/​connect_service.go Keeps systemd services Linux-only.
internal/​commands/​connect_service_test.go Tests service platform gates.
internal/​commands/​connect_run.go Enables connector execution on macOS.
internal/​commands/​connect_run_test.go Tests Linux and macOS support.
internal/​commands/​connect_operator_test.go Updates doctor platform expectations.
internal/​cli/​token_handover_platforms_test.go Tracks the shared sealing implementation.
internal/​cli/​inherited_fds_seal.go Shares descriptor sealing across Linux and macOS.
internal/​cli/​inherited_fds_other.go Excludes macOS from the no-op implementation.
internal/​cli/​inherited_fds_linux.go Reuses shared sealing helpers.
internal/​cli/​inherited_fds_darwin.go Implements macOS descriptor sealing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/cli/inherited_fds_darwin.go Outdated
Comment thread internal/commands/mcp_token_other.go Outdated
Comment thread internal/commands/connect_run.go

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 20 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread internal/cli/inherited_fds_darwin.go Outdated
Comment thread internal/connector/driver/worker_unix.go
Comment thread internal/connector/tokensocket_darwin.go
Comment thread internal/commands/connect_run.go
Comment thread internal/commands/connect_service.go
Comment thread internal/commands/connect_service_test.go
Comment thread internal/connector/tokensocket_test.go Outdated
Comment thread internal/commands/mcp_token_sealed.go
Comment thread internal/commands/connect_run.go Outdated
Comment thread internal/cli/inherited_fds_darwin.go
- The /dev/fd fallback fails closed: it refuses when the descriptor limit can't
  be read or is too large to scan, rather than checking a smaller range.
- The zombie-only group refusal applies on macOS alone: on Linux, EPERM is a
  group this process may not signal, and /proc mounted hidepid can't list its
  members to say otherwise.
- The token's taker is the peer the trust check named before the write, not a
  second reading: a server that reads its token and closes at once would come
  back unnamed on macOS and hold the attempt.
- The other platforms' token refusal names --connect-token-fd.
- Both skills say the connector runs on Linux and macOS.
- Comments and names that still said Linux only.
- Tests: macOS sealing (/dev/fd walk and fallback), the token reader and the MCP
  connect flow now build and run on macOS too.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 01:05
@github-actions github-actions Bot added the skills Agent skills label Oct 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

macOS-specific kernel branches need regression coverage, and doctor still contains stale Linux-only guidance.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
Resolved since last review (3)

Comment thread internal/commands/connect_doctor.go
Comment thread internal/connector/driver/worker_unix.go
Comment thread internal/connector/tokensocket_darwin.go

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 16 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread internal/cli/inherited_fds_darwin.go Outdated
Comment thread skills/basecamp-connect/SKILL.md Outdated
Comment thread internal/connector/driver/worker_unix.go
Comment thread internal/commands/connect_doctor.go
Comment thread internal/cli/inherited_fds_darwin_test.go Outdated
- connect doctor's Platform hint and comment name Linux and macOS.
- The /dev/fd fallback runs only when /dev/fd can't be opened; a descriptor the
  walk fails to seal is that error. Its messages tell a zero limit from one too
  large to scan.
- macOS tests for a zombie-only group (no such group) beside one with a live
  member (still signaled), and for a real socket peer that closed (gone, with
  its user).
- The connect skill no longer mentions the hidden service.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 01:36

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread internal/connector/tokensocket_darwin_test.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The macOS fallback can miss inherited descriptors above a subsequently lowered soft descriptor limit.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (3)

Comment thread internal/cli/inherited_fds_darwin.go Outdated
The descriptor limit doesn't bound which descriptors are open: a parent can
open one high and then lower the limit. /dev/fd is always mounted on macOS,
so there's no fallback; if it can't be read, sealing fails.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 01:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The token receiver’s process identity is captured after delivery, leaving a PID-reuse race that can record the wrong process.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)

Comment thread internal/connector/tokensocket.go Outdated
Once the worker has read its token and gone, its pid could belong to another
process. Looked up before the write, the taker is the process that was
checked.
Copilot AI balanced review requested due to automatic review settings October 1, 2026 02:11

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread internal/connector/tokensocket_test.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approved

The platform expansion fails closed, preserves token isolation, and includes targeted Darwin regression coverage.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@robzolkos
robzolkos merged commit 8c59125 into main Oct 1, 2026
32 checks passed
@robzolkos
robzolkos deleted the connect-on-mac branch October 1, 2026 02:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations skills Agent skills tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants