transport: derive the daemon endpoint from the uid, not from a variable #281

Merged
natsukium merged 8 commits from issue-275 into main 2026-09-15 10:45:21 +09:00
Owner

Fixes #275.

Reworked to plan v3 (issue #275 comment 2818), which replaces the rev-12 relay-probe design this branch
carried before.

What changes

The endpoint is a function of the uid first, the environment second. SocketPath::resolve(uid) in
felis-transport is the single derivation, shared by serve, local clients and the relay:

OS order
Linux $XDG_RUNTIME_DIR/felis when set and non-empty → /run/user/<uid>/felis when it exists, is a non-symlink directory owned by the uid and is mode exactly 0700${TMPDIR:-/tmp}/felis.<uid>
macOS $TMPDIR/felis.<uid> when set and non-empty → <confstr(_CS_DARWIN_USER_TEMP_DIR)>/felis.<uid>/tmp/felis.<uid>
Windows unchanged (SID-derived pipe name)

A /run/user/<uid> that exists but fails validation is a hard resolution error naming the reason and the
recovery, never a fall-through: falling back to /tmp is exactly the split this change removes. An empty
TMPDIR/XDG_RUNTIME_DIR normalizes to unset; a relative XDG_RUNTIME_DIR is treated as unset and warned
about once per process.

A raw connect error is classified before anything spawns. Only io::ErrorKind::NotFound | ConnectionRefused
licenses a spawn. The same rule applies on the local dial, in connect_or_spawn_daemon's fork path, in the
systemd hand-off, and in the relay; post-connect failures keep spawn-and-retry.

The relay goes back to a single endpoint. felis-daemon relay resolves the same endpoint every other
process does and stays on it: no endpoint topology, no sibling selection, no probing another endpoint to decide
where to spawn.

Doctor reports what another environment on this host could reach. A daemon-sibling row is emitted per
sibling candidate that answers, with the target/default/sibling provenance rules of the plan's Decision 3, plus a
bounded dial (dial_bounded) that names which phase a deadline caught.

Out of scope

Daemon lifetime after endpoint loss (a daemon whose runtime directory logind removed at last logout) is not
addressed here. Issue #282 owns it: no watch/drain, no Listener::drop change, no agent-link change, no
stale-stamp recovery is in this branch. The how-to and CHANGELOG tell hosts that serve persistent daemons to
loginctl enable-linger, which is the operational answer until #282 lands.

Gates

just check and just check-windows pass locally.

Fixes #275. Reworked to **plan v3** (issue #275 comment 2818), which replaces the rev-12 relay-probe design this branch carried before. ## What changes **The endpoint is a function of the uid first, the environment second.** `SocketPath::resolve(uid)` in felis-transport is the single derivation, shared by `serve`, local clients and the relay: | OS | order | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Linux | `$XDG_RUNTIME_DIR/felis` when set and non-empty → `/run/user/<uid>/felis` when it exists, is a non-symlink directory owned by the uid and is mode exactly `0700` → `${TMPDIR:-/tmp}/felis.<uid>` | | macOS | `$TMPDIR/felis.<uid>` when set and non-empty → `<confstr(_CS_DARWIN_USER_TEMP_DIR)>/felis.<uid>` → `/tmp/felis.<uid>` | | Windows | unchanged (SID-derived pipe name) | A `/run/user/<uid>` that exists but fails validation is a hard resolution error naming the reason and the recovery, never a fall-through: falling back to `/tmp` is exactly the split this change removes. An empty `TMPDIR`/`XDG_RUNTIME_DIR` normalizes to unset; a relative `XDG_RUNTIME_DIR` is treated as unset and warned about once per process. **A raw connect error is classified before anything spawns.** Only `io::ErrorKind::NotFound | ConnectionRefused` licenses a spawn. The same rule applies on the local dial, in `connect_or_spawn_daemon`'s fork path, in the systemd hand-off, and in the relay; post-connect failures keep spawn-and-retry. **The relay goes back to a single endpoint.** `felis-daemon relay` resolves the same endpoint every other process does and stays on it: no endpoint topology, no sibling selection, no probing another endpoint to decide where to spawn. **Doctor reports what another environment on this host could reach.** A `daemon-sibling` row is emitted per sibling candidate that answers, with the target/default/sibling provenance rules of the plan's Decision 3, plus a bounded dial (`dial_bounded`) that names which phase a deadline caught. ## Out of scope Daemon lifetime after endpoint loss (a daemon whose runtime directory logind removed at last logout) is **not** addressed here. Issue #282 owns it: no watch/drain, no `Listener::drop` change, no agent-link change, no stale-stamp recovery is in this branch. The how-to and CHANGELOG tell hosts that serve persistent daemons to `loginctl enable-linger`, which is the operational answer until #282 lands. ## Gates `just check` and `just check-windows` pass locally.
A process whose environment lacks the login manager's per-user variable
resolves the environment-free fallback and finds it cold, with no way to
tell that the daemon it wants is answering at the login manager's
location instead. Deciding that needs the pair as data: both endpoints
derived from the uid alone, and a classification of whatever the
resolver produced as one of them or neither.

The pair is derived without reading the environment on purpose. The
resolved default already encodes what the environment said, so a second
read could only disagree with it, and a custom directory has to stay
distinguishable from the two canonical ones: it is an opt-out, and
nothing may probe or report beside it.

An empty variable now resolves like an unset one. `TMPDIR=` used to fail
resolution as "not an absolute path" before anything could be dialed, so
no daemon can be missed by treating it as absence.

Assisted-by: Claude Code
A relay choosing between two endpoints, and a `doctor` row reporting on
one, both need the same question answered: is a felis daemon answering
here? A bare connect cannot answer it. Connecting proves only that
something accepted, and a peer that accepts and then says nothing would
hold an unbounded dial forever, so the probe runs the version preface
and one observer handshake under a deadline.

The outcomes are split three ways rather than two because "no daemon" is
the one answer that licenses starting a second one. `ENOENT` and
`ECONNREFUSED` prove an endpoint is cold; `EACCES`, a descriptor
shortage, a silent listener and a peer that speaks no felis do not, and
folding them into "absent" is how a process ends up spawning beside a
daemon it simply failed to reach.

Every negotiation verdict short of an accept counts as live: a refusal,
a preface status only a newer felis knows, an accept naming a major this
build never offered, and a `Refused` frame of any reason are all answers
from a running daemon.

Assisted-by: Claude Code
An SSH server that runs no login-manager PAM module (Tailscale SSH on
Linux, every SSH login on macOS) hands the relay an environment without
`XDG_RUNTIME_DIR` or launchd's `TMPDIR`. The relay then resolves the
environment-free fallback, finds it cold, and starts a second daemon
there while every existing session stays on the first one. A read verb
crossing the same hop reports "no daemon on this host" with the user's
daemon running a few paths away.

The resolved endpoint is still dialed first and a daemon answering there
is still taken, so the frozen default rule is untouched and an upgraded
relay cannot miss a daemon an older one would have found. Only when that
endpoint is cold, and only when it is the fallback, does the relay probe
the login manager's location: the login manager's own location has no
sibling to prefer, and a custom `XDG_RUNTIME_DIR` or `TMPDIR` is an
explicit opt-out the environment already decided.

The relay bridges to a verified sibling with an ordinary fresh
connection and no gate of its own. A partial gate cannot bound a peer
that stops after a valid accept, and a full one would make the relay
parse the client's handshake, which it must not do; a deadline on the
client's side would fire under SSH's own password and host-key prompts.
What remains is the exposure the resolved path already has.

A connect error other than "nothing is listening" no longer licenses a
spawn. `EACCES` on the resolved path is not evidence that the path is
free, and spawning on it puts a second daemon beside one the relay
merely failed to reach.

Assisted-by: Claude Code
A second daemon at the other per-user endpoint is invisible today:
`doctor` dials the endpoint this process resolved and says nothing about
the one a process with a different environment would have reached, which
is exactly the split a relay under an SSH server without a login-manager
PAM module produces. The new `daemon-sibling` row probes that endpoint
and reports what answered.

The row states only what was observed. `DaemonStatus` carries no pid and
no endpoint, so nothing on the wire can prove that two answering paths
are two daemons; the row therefore never says so and never recommends
stopping one, and it points at `sessions list` on both paths as the way
to find out.

Provenance decides whether the pair applies at all, because path
equality cannot: an explicit `--socket` or a `FELIS_SOCKET` stamp can
name the platform default and still be one exact address the caller
chose. `resolve_local_socket_from` therefore reports where the path came
from, and the row and the bounded dial apply to the default alone.

The primary row's dial gains a deadline and a three-way partition on the
same default-local target, so a silent listener no longer hangs `doctor`
and no longer reads as "not running". `doctor --host` keeps its
unbounded dial: its preface read sits behind ssh's password and host-key
prompts, and a felis deadline there would fire under the prompt.

Two answers that used to fall into the "not running" catch-all now say
so on every target, because they come from a daemon rather than from a
failed dial: a preface status this build cannot name, and a `Refused`
frame of any reason.

Assisted-by: Claude Code
docs: record the relay's endpoint selection and the sibling row
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m9s
darwin / detect relevant changes (pull_request) Successful in 4s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m5s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 2m34s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 40s
pr / nix flake check (pull_request) Successful in 2m21s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m43s
pr / wire schema is compatible with the base (pull_request) Successful in 11s
windows / cargo nextest (Windows) (pull_request) Failing after 3m46s
windows / frontend smoke (Windows) (pull_request) Has been skipped
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
54534a14d8
The normative rule (which endpoint a relay dials, what the probe can
establish, and what each outcome leads to) belongs in the IPC reference
beside the rest of the cross-host carrier; the argument for placing it
in the relay rather than in the default rule, the rejected alternatives,
and the two races that survive belong in the architecture page.

The how-to gains the Linux counterpart to its macOS `TMPDIR` note.
Tailscale SSH is an ordinary way to reach a host and never creates a
pam_systemd session environment, so the split it produces needs the same
treatment the macOS one already had, plus the operator recipe for a host
that ended up with a daemon at each endpoint.

Assisted-by: Claude Code
transport: run the empty-TMPDIR test on Unix only
All checks were successful
bench / Criterion regression gate (pull_request) Successful in 2m7s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
pr / nix flake check (pull_request) Successful in 45s
windows / cargo clippy (Windows cross) (pull_request) Successful in 11s
windows / cargo nextest (Windows) (pull_request) Successful in 4m23s
windows / frontend smoke (Windows) (pull_request) Successful in 1m40s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m14s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m4s
pr / wire schema is compatible with the base (pull_request) Successful in 12s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m8s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
5dc9e78c26
`tmpdir_fallback` treats an empty `TMPDIR` as unset and lands on
`/tmp`, but `/tmp` is not an absolute path on Windows, where the
resolver never takes this branch (the daemon endpoint is a named pipe),
so the Windows nextest job failed on a case it does not implement.

Assisted-by: Claude Code
The probe decoded the reply frame's body as a `ConnMsg` without looking
at the frame's kind, so a peer that writes a `Welcome` or `Refused`
payload under any other family's kind passed as a live daemon. That is
the one judgment a relay acts on when it decides not to start a daemon
and to bridge to the endpoint instead, so the bar has to be the one the
client's own handshake uses: the kind first, the body second.

Assisted-by: Claude Code
The endpoint pair exists only where an environment variable can move the
per-user socket. Windows derives its pipe name from the user's SID, so
there is nothing to choose between and nothing to probe, yet a resolved
default was routed through the pair's stricter dial anyway: a carrier
error other than "nothing is listening", such as an exhausted
`ERROR_PIPE_BUSY` retry, ended the dial instead of reaching the
connect-or-spawn it used to. Without a topology the resolved default is
now one endpoint like any other, which is the explicit `--socket` path.

Assisted-by: Claude Code
cli: word the sibling row from what the primary dial established
All checks were successful
bench / Criterion regression gate (pull_request) Successful in 2m5s
darwin / detect relevant changes (pull_request) Successful in 4s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m18s
pr / nix flake check (pull_request) Successful in 44s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m58s
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 12s
windows / frontend smoke (Windows) (pull_request) Successful in 1m46s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
pr / wire schema is compatible with the base (pull_request) Successful in 10s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 54s
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 3m43s
ab270912ee
The sibling row read the primary dial as a boolean, so an endpoint that
could not be dialed and one that answered nothing both collapsed into
"not live" and the row announced "no daemon at <path>" beside them. That
asserts the one thing those dials did not establish. The primary row now
carries its outcome, and only a dial that proved the endpoint cold gets
the "no daemon" wording; the rest say the endpoint could not be verified
and why.

A dial that runs out of time says so and nothing more. One deadline
covers the connect and the handshake, so an expiry cannot tell which of
them was still running, and the old rendering claimed a listener was
there whenever it fired.

Assisted-by: Claude Code
The warning that names the endpoint a daemon is about to be started on
was emitted before the last connect attempt, so a daemon that bound the
endpoint between the selection dial and that connect left the operator
with a logged spawn that never happened. Emitting it after the final
pre-spawn connect fails makes the line report what the relay actually
does next.

Assisted-by: Claude Code
docs: scope the relay's endpoint pair to the platforms that have one
Some checks failed
bench / Criterion regression gate (pull_request) Failing after 2m11s
darwin / detect relevant changes (pull_request) Successful in 3s
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 52s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 4s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 3m39s
windows / frontend smoke (Windows) (pull_request) Successful in 1m35s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m10s
pr / nix flake check (pull_request) Successful in 31s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m37s
pr / wire schema is compatible with the base (pull_request) Successful in 11s
windows / cargo clippy (Windows cross) (pull_request) Successful in 12s
aba78d9fc2
The endpoint-selection rules were written as if every default relay dial
had a login manager location to probe, but a Windows pipe name derives
from the user's SID and no environment moves it, so a relay there has a
single endpoint and keeps the plain connect-or-spawn. Stating the probe,
the connect-error partition and REQ-009b as universal would have made
the normative text describe behavior no Windows build has.

Assisted-by: Claude Code
macOS derives the login manager's per-user endpoint from
confstr(_CS_DARWIN_USER_TEMP_DIR), which can answer with nothing, with
more bytes than it sized, or with a relative path. That failure used to
be indistinguishable from a platform that has no such location at all,
so a relay resolving the cold fallback found no candidate to probe and
spawned there with the pair's other half never examined, which is the
roster split this selection exists to prevent.

The topology now carries the derivation's outcome, and a relay meeting an
endpoint it could not name ends the dial with the reason, for --no-spawn
and spawning dials alike. Splitting the judgment of confstr's two return
values out of the call itself puts every failing answer within reach of a
test on the one platform that has it.

Assisted-by: Claude Code
The daemon row's 2 s deadline wrapped the connect and the handshake as
one step, so an expiry could say only that the dial had not finished:
a silent listener, which is the case the deadline exists for, rendered
as a line claiming nothing about the endpoint even though the connect
had succeeded and proved something was there.

client-core now owns a bounded dial that keeps one overall deadline
across its existing connect and handshake steps and names the phase the
expiry caught, so doctor renders a connect-phase expiry as an endpoint it
could not dial and a handshake-phase one as a peer that did not answer as
a felis daemon. Doing it there, rather than by timing the two steps from
the CLI, keeps the carrier halves and the handshake private to the crate
that owns them.

The sibling row gains the reading of an endpoint whose derivation failed:
saying nothing would leave a pair whose other half was never looked at
reported as if it had been.

Assisted-by: Claude Code
docs: state what an endpoint that could not be named means
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m8s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m19s
pr / nix flake check (pull_request) Successful in 31s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m39s
pr / wire schema is compatible with the base (pull_request) Successful in 11s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 51s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 4s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Failing after 8s
windows / cargo nextest (Windows) (pull_request) Successful in 3m42s
windows / frontend smoke (Windows) (pull_request) Successful in 1m47s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
1331ca5647
The endpoint-selection rules recorded only "absent" and "could not be
dialed or verified" as outcomes, which left the third one a reader would
hit on macOS undocumented: a location the OS has and the relay could not
derive, where neither spawning nor reporting no daemon is warranted.

The doctor row's deadline is documented as the phase it caught rather
than as a fifth outcome that claims nothing, since the rendering now
follows the connect and the handshake apart.

Assisted-by: Claude Code
The platforms with neither a login manager location nor a way to fail
naming one return a Result that is always Ok, which the cross-compiled
clippy run rejects as an unnecessary wrapper. The signature is shared so
callers reason about one shape on every target, which is the same reason
the Linux implementation already carries this expectation.

Assisted-by: Claude Code
The connect immediately before the spawn discarded its error, so an
endpoint that became undialable after the selection judged it absent —
a directory mode change, an exhausted descriptor table — was spawned on
anyway, putting a second daemon beside one the relay had merely failed
to reach. That is the roster split the selection exists to prevent, and
the earlier dial's own error partition was the only thing guarding it.

Only a resolved default arrives here already judged absent, so only it
re-judges: an explicit --socket and a topology-free Windows endpoint
never made that judgment and keep their plain connect-or-spawn.

Assisted-by: Claude Code
docs: name both conditions that emit the doctor sibling row
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m5s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m8s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 51s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
pr / nix flake check (pull_request) Successful in 32s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m41s
pr / wire schema is compatible with the base (pull_request) Successful in 10s
windows / cargo clippy (Windows cross) (pull_request) Successful in 12s
windows / cargo nextest (Windows) (pull_request) Successful in 3m31s
windows / frontend smoke (Windows) (pull_request) Successful in 1m32s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
b0f833188e
The row's description promised it only where a daemon answers the other
canonical endpoint, while the row is also what reports an endpoint the
OS has and this build could not name. A reader checking the table
against a report would have taken that row for a daemon sighting.

Assisted-by: Claude Code
Three diagnostics wrapped their literal without a continuation escape,
so the relay's stderr, doctor's report, and one endpoint-derivation
reason carried ten to twenty spaces of Rust indentation into the text a
user reads. The tests that already cover two of those messages now
assert that no double space reaches the rendered string, which is what
the eye misses on review.

Assisted-by: Claude Code
`XDG_RUNTIME_DIR=` never reached a working-directory-relative socket:
the `directories` crate rejects a value that is not absolute, so the
empty case falls through to the same fallback an unset variable takes.
The `TMPDIR=` failure is the one this normalization prevents.

Assisted-by: Claude Code
docs: separate resolving an endpoint from deciding which daemon answers
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 3s
bench / Criterion regression gate (pull_request) Successful in 2m5s
pr / nix flake check (pull_request) Successful in 30s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m37s
pr / wire schema is compatible with the base (pull_request) Successful in 13s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 53s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m4s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 50s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 4s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 12s
windows / cargo nextest (Windows) (pull_request) Successful in 3m32s
windows / frontend smoke (Windows) (pull_request) Successful in 1m30s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
ae85a1c6ba
The socket-layout bullet said two processes whose environments disagree
reach different daemons, full stop, which a reader would carry into the
relay: a relay that resolves the environment-free fallback and finds it
cold bridges to a daemon at the login manager's endpoint instead. The
rule holds for the surfaces that bind and dial the path they resolved,
so it is now stated of those, with the two surfaces that consult the
pair named beside it.

Assisted-by: Claude Code
natsukium force-pushed issue-275 from ae85a1c6ba
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 3s
bench / Criterion regression gate (pull_request) Successful in 2m5s
pr / nix flake check (pull_request) Successful in 30s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m37s
pr / wire schema is compatible with the base (pull_request) Successful in 13s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 53s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m4s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 50s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 4s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 12s
windows / cargo nextest (Windows) (pull_request) Successful in 3m32s
windows / frontend smoke (Windows) (pull_request) Successful in 1m30s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
to ba17042c83
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m5s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 55s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m4s
pr / nix flake check (pull_request) Successful in 34s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m45s
pr / wire schema is compatible with the base (pull_request) Successful in 8s
windows / cargo clippy (Windows cross) (pull_request) Successful in 14s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m0s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 4m35s
windows / frontend smoke (Windows) (pull_request) Successful in 5m30s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
2026-09-15 00:07:47 +09:00
Compare
natsukium changed title from daemon: probe the login manager's endpoint before a relay autospawns (#275) to transport: derive the daemon endpoint from the uid, not from a variable 2026-09-15 00:07:59 +09:00
natsukium force-pushed issue-275 from ba17042c83
All checks were successful
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m5s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 55s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m4s
pr / nix flake check (pull_request) Successful in 34s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m45s
pr / wire schema is compatible with the base (pull_request) Successful in 8s
windows / cargo clippy (Windows cross) (pull_request) Successful in 14s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m0s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 4m35s
windows / frontend smoke (Windows) (pull_request) Successful in 5m30s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
to a772dd17d4
Some checks failed
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m18s
pr / nix flake check (pull_request) Successful in 33s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m51s
pr / wire schema is compatible with the base (pull_request) Successful in 9s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m15s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 5s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 18s
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m8s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 6m39s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
2026-09-15 00:34:53 +09:00
Compare
natsukium force-pushed issue-275 from a772dd17d4
Some checks failed
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m18s
pr / nix flake check (pull_request) Successful in 33s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m51s
pr / wire schema is compatible with the base (pull_request) Successful in 9s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m15s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 5s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 18s
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Successful in 2m8s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 6m39s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
windows / frontend smoke (Windows) (pull_request) Has been cancelled
to 8bcc200614
All checks were successful
bench / Criterion regression gate (pull_request) Successful in 2m14s
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / detect relevant changes (pull_request) Successful in 8s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m13s
pr / nix flake check (pull_request) Successful in 39s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m49s
windows / detect relevant changes (pull_request) Successful in 4s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 4m15s
pr / wire schema is compatible with the base (pull_request) Successful in 9s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m4s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 15s
windows / frontend smoke (Windows) (pull_request) Successful in 1m49s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
2026-09-15 00:49:32 +09:00
Compare
natsukium deleted branch issue-275 2026-09-15 10:45:21 +09:00
Sign in to join this conversation.
No description provided.