[Post-v0.1/P2] Make the macOS daemon endpoint independent of SSH TMPDIR #41
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#41
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
現状
${TMPDIR}/felis.<uid>/配下(local_socket::resolve_local_socket)。GUI セッションのTMPDIRはgetconf DARWIN_USER_TEMP_DIR由来だが、SSH ログインのTMPDIRは未設定で/tmpにフォールバックする。結果、felis --host macで SSH 経由の daemon と GUI の daemon が別 socket(別 daemon)になる。how-to/attach-over-ssh.md"Attaching to a Mac: give the SSH login a TMPDIR" でexport TMPDIR=$(getconf DARWIN_USER_TEMP_DIR)を.zshrcに書けと案内。XDG_RUNTIME_DIR(Linux) も同様に SSH セッションで未設定になり得るが、felis はフォールバックで~/.local/state等を使うため二重化は起きにくい。macOS のTMPDIRだけが顕在化。doctorのdaemon行は手元の daemon しか見ない。問い(1.0 でしか変えられない)
socket 導出を
TMPDIR依存のまま凍結するか? 1.0 で決定的な場所($HOME起点)に直すか?破壊的変更案
$HOME起点に移す。~/.felis/daemon.sockまたは~/Library/Application Support/felis/daemon.sockに統一。TMPDIR/XDG_RUNTIME_DIRの有無に依存しない。既存のTMPDIR依存は deprecated としてフォールバックで読むが、新規は$HOME起点のみ。local_socket::resolve_local_socketとdocs/reference/cli.md"--socket" デフォルト表を同時更新。TMPDIR未設定時に警告/エラーで止める。 daemon 起動時とfelis --hostの relay 接続時にTMPDIRが空ならtracing::warn+doctorのdaemon行でwarnを出す。how-to/attach-over-ssh.mdの手動exportを必須化ではなく検出可能に。~/Library/Application Support/felis/daemon.sockと${TMPDIR}/felis.<uid>/daemon.sockの両方をチェックし、既存 daemon が見つかればそちらを案内して exit 2。--socket明示時は対象外。判定基準
~/.ssh/configにHost macとForwardAgent yesだけを書いた状態で、felis --host macとローカルのfelisが同一 daemon(同一daemon statusのsessions行)を見に行くこと。reference/cli.md"--socket" のデフォルト表とexplanation/architecture/daemon-client-split.mdの daemon 配置記述が、実装のresolve_local_socketと一致していること。対象ファイル
crates/felis-client-core/src/local_socket.rs,crates/felis-daemon/src/main.rs(--socket既定),crates/felis-daemon/src/serve.rs,docs/reference/cli.md,docs/how-to/attach-over-ssh.md,docs/explanation/architecture/daemon-client-split.md,docs/explanation/architecture/ipc.mdcc @natsukium
[v0.1/SSH Review] macOS TMPDIR 非共有による daemon 二重起動to [Post-v0.1/P2] Make the macOS daemon endpoint independent of SSH TMPDIRTriaged to post-v0.1/P2. The duplicate-daemon behavior is a valid macOS lifecycle bug, but v0.1.0 claims support only for
x86_64-linux; changing the default endpoint must not block that tag. The implementation issue remains open because changing it after macOS support is claimed could orphan live sessions.Triage plan (2026-09-03)
Source-grounded triage against
mainat69076d42, reviewed through seven rounds of an independent reviewer (pisol/luna) until it passed with no findings. The dependency order that supersedes the tracker's is posted on #12. Where a "Review amendments" section below conflicts with an earlier section, the amendment is the decision.Claim check
The bug is real and reproducible from the source; one supporting claim about Linux is wrong.
crates/felis-transport/src/socket.rs:28-48. On macOSSocketPath::resolvegoes unconditionally totmpdir_fallback(uid, $TMPDIR)(:29-31), which is${TMPDIR:-/tmp}/felis.<uid>/daemon.sock(:39-47). On Linux it first triesdirectories::ProjectDirs::runtime_dir()(i.e.$XDG_RUNTIME_DIR/felis) and then the same TMPDIR fallback (:32-37).~/.local/stateetc." is wrong: an SSH login withoutXDG_RUNTIME_DIRgets/tmp/felis.<uid>on Linux too. It rarely manifests because pam_systemd setsXDG_RUNTIME_DIRfor SSH logins on systemd hosts, but the fix should not be macOS-only in its reasoning.crates/felis-client-core/src/local_socket.rs:25-33(--socket, thenFELIS_SOCKET, then the platform default), documented atdocs/reference/cli.md:602-612. The remote side of--hostisfelis-daemon relay, which resolvesdefault_socket_path()in its own (sshd-provided) environment (crates/felis-daemon/src/main.rs:89-96) and autospawnsfelis-daemon serve --socket <that path>(relay.rs:103-117). A GUI-launched daemon resolves against the launchdTMPDIR. Two paths, two daemons — confirmed.docs/how-to/attach-over-ssh.md:176-183. Confirmed.doctordials only the resolved target (crates/felis-cli/src/cli_doctor.rs:128-160) andDaemonStatus(connector.rs:683-690) carries no endpoint, so a second daemon is invisible — confirmed.0700dir /0600socket / "pathname sockets under the user's runtime dir" is atdocs/explanation/security-model.md:267-290; no macOS-specific placement rationale is recorded anywhere, so the current TMPDIR choice is an undocumented default rather than a decision.x86_64-linux(docs/how-to/install.md:143-148:aarch64-darwin"Nothing runs on macOS"), so the macOS default endpoint is not part of any compatibility promise the tag makes.Verdict
defer (keep post-v0.1.0, P2) — with a hard gate the label set should carry: this must land before the first release that claims macOS support, because that release is the one after which moving the default orphans live sessions. Deferral past v0.1.0 costs nothing: the freeze boundary lists "documented environment variables", and
TMPDIRis an OS variable felis merely consumes;cli.md:602documents the resolution order, not the macOS path. #12's "Contract freeze boundary" does not mention the endpoint default, and it should not be added.Recommend option A in a specific form, plus a slice of C for observability; reject B as the primary fix (a warning still leaves two daemons).
Approach
confstr(_CS_DARWIN_USER_TEMP_DIR)— the same per-user, mode-0700 directory launchd puts inTMPDIR(getconf DARWIN_USER_TEMP_DIRis its shell spelling), stable across GUI and SSH logins, and a runtime location rather than a persistent one (~/Library/Application Supportis backed up/synced and is whereconfig.tomlalready lives,install.md:58; sockets do not belong there). Order on macOS becomes--socket→FELIS_SOCKET→confstrdir →$TMPDIR→/tmp. This keeps the GUI-side path identical to today (launchd'sTMPDIRis that directory), so no existing GUI session is orphaned; only the SSH-side daemons move, and those are the ones the bug created.confstris one libc FFI call infelis-transport, which already carries an audited#[allow(unsafe_code)]site forgetpeereid(CLAUDE.md"Workspace policy";felis-transport/Cargo.toml:28-37already depends onlibcfor the same reason) — add it beside that one with a// SAFETY:comment.XDG_RUNTIME_DIRis unset, probe/run/user/<uid>before the TMPDIR fallback. Optional; it closes the same class of bug the issue mis-described.doctor'sdaemonrow also connect-probes the legacy path(s) (${TMPDIR:-/tmp}/felis.<uid>/daemon.sockwhen it differs from the resolved one) and reports "a second daemon is serving " as awarnrow, reusing the probeservealready uses to avoid stealing a live socket (security-model.md:288-300). No exit-2, no automatic takeover: a live daemon with sessions must never be unlinked (same rule).felis-daemon servelogs the resolved dir at startup already; add the derivation source (confstr/TMPDIR/fallback) to that line.docs/reference/cli.md:602-612(the default per platform, now stated),docs/how-to/attach-over-ssh.md:176-183(delete theTMPDIRrecipe, or keep it as "older builds"),docs/explanation/security-model.md"Daemon IPC" (record the decision: env-independent per-user runtime dir; rejected$HOME-rooted persistent dirs and the warn-only option; Revisit if macOS ever drops_CS_DARWIN_USER_TEMP_DIR),docs/explanation/architecture/ipc.md:427-428,456-457(generalize "$XDG_RUNTIME_DIR" to "the per-user runtime dir"),docs/reference/terminal-identity.mdif it states the path,CHANGELOG.md(macOS: "the daemon endpoint no longer depends onTMPDIR; an SSH-spawned daemon on the old/tmp/felis.<uid>path keeps running — drain it withfelis --socket /tmp/felis.<uid>/daemon.sock sessions listthenpkill").skills/felisdoes not state the path (skills/felis/SKILL.md:310,374only mention "non-default socket"), so no change unless the doctor row text is documented there.socket.rsunit tests (:95-106cover the TMPDIR fallback) gain acfg(target_os = "macos")test that resolution withTMPDIRunset equals resolution with it set to the confstr dir; a Linux test for the/run/user/<uid>probe. macOS has no CI runtime (install.md:145), so the acceptance criterion in the issue (samedaemon statussession list fromfelis --host macand localfelis) is verified by hand with thefelis-macos-gui-debugskill and recorded in the PR.Dependencies
SocketPath::resolve. Land after the tag and before any macOS support claim (theaarch64-darwinrow ininstall.md:145flipping to "Supported" is the trigger).Risk/effort
S–M. One new audited unsafe site in felis-transport; the migration risk (daemons already serving on
/tmp/felis.<uid>from SSH relays) is handled by the doctor probe and the CHANGELOG drain note rather than by automatic takeover.Labels
Keep
priority/P2, post-v0.1.0. Add a note (or label, if one exists) "blocks macOS support claim" so it cannot be forgotten wheninstall.md's support table changes. Correct the issue body's Linux fallback claim (~/.local/state→${TMPDIR:-/tmp}/felis.<uid>,socket.rs:37-47).Review amendments (round 3)
/run/user/<uid>probe is withdrawn. Linux is the supported platform, and its default endpoint ($XDG_RUNTIME_DIR/felis, else${TMPDIR:-/tmp}/felis.<uid>,socket.rs:28-47;workspace.md:235-239) is observable behavior inside #12's freeze boundary: changing it after the tag would make an upgraded client miss a running daemon and autospawn a second one. Record in the plan and in the #41 body that the Linux rule is frozen as-is for v0.1.0 (a one-line addition to the reference page's endpoint paragraph). The macOS change stays post-v0.1.0 and, if it ever needs a Linux counterpart, must probe the legacy path and never bind over a live socket.natsukium referenced this issue2026-09-14 17:06:21 +09:00
The macOS half of the endpoint topology landed in #281 (for #275), connect-only:
felis-transportnow derivesconfstr(_CS_DARWIN_USER_TEMP_DIR)/felis.<uid>/daemon.sockas the login manager's location, and bothfelis-daemon relayandfelis doctorprobe it when the resolved default is the environment-free/tmp/felis.<uid>fallback. Nothing binds there and the macOS default endpoint rule is untouched, so no GUI session moves.That complements this issue rather than pre-empting it: moving the macOS default to the confstr directory is still #41's own decision, and the round-3 amendment's condition on any Linux counterpart ("must probe the legacy path and never bind over a live socket") is what #281 implements. The confstr call is the one audited
#[allow(unsafe_code)]site with a// SAFETY:comment that the triage plan sanctioned; it is unit-tested undercfg(target_os = "macos")but was not hand-verified on a Mac in that PR.Resolved by #275 plan v3 (comment #275 (comment)), the macOS half of option A: an absent
TMPDIRresolves toconfstr(_CS_DARWIN_USER_TEMP_DIR)/felis.<uid>, the same directory launchd exports as the GUI session'sTMPDIR, so an SSH login's relay reaches the GUI daemon without exporting anything. A setTMPDIRstays authoritative, so the GUI daemon does not move and a custom value is still honored. Option C's observability survives as thefelis doctordaemon-siblingrow (it reports a daemon an older build left on/tmp/felis.<uid>). The round-3 "frozen at v0.1.0" amendment is withdrawn: no tag exists and the derivation is now stated in #12's freeze list as of the #275 change. Closing when the reworked PR #281 merges.