Skip to content

fix: say how the semantic provider died, not just that it stopped - #5

Merged
CesarManzoCode merged 1 commit into
mainfrom
claude/rust-analyzer-death-diagnostics-2krdy2
Aug 30, 2026
Merged

fix: say how the semantic provider died, not just that it stopped#5
CesarManzoCode merged 1 commit into
mainfrom
claude/rust-analyzer-death-diagnostics-2krdy2

Conversation

@CesarManzoCode

Copy link
Copy Markdown
Owner

On Fedora on 2026-08-30 stage 58 reported analyzer_starts=1 and then rust-analyzer did not answer: initialize: the server stopped, and there was nothing else. ausearch -m SECCOMP over the exact seconds of the run showed no SECCOMP, no AVC, no SELinux denial and no other kill. So the machine held exactly one sentence about the death, and that sentence is rule 10's failure shape: it says the reading failed and nothing about what happened. A process killed by the seccomp filter, a process that could not find its toolchain, and a process that panicked all close a pipe, and from request_once all three were the same event.

Two things the kernel already knew were being thrown away.

How the process ended. Analyzer holds the Child and never asked it. epitaph waits half a second — the pipe closing and the process being reaped are two events in that order, and asking at the instant of the first answers "still running" for a process that has already died — and then says the exit status, or the signal and its name. SIGSYS is 31, and 31 on its own tells the person reading nothing; a process killed by the filter dies of exactly that signal, so if that is what happened on Fedora the next run will say so in the refusal itself.

What it wrote on the way out. launch::spawn has always given a confined program a stderr pipe and nothing on this path ever read it — which is not only lost evidence but a latent hang, because a pipe whose reader never empties it blocks the writer on a full buffer, and a server stopped mid-indexing looks exactly like one that hung. It is drained continuously now into a 4 KiB tail: the last bytes, because a server that logs its way through an indexing pass would otherwise fill any buffer with progress before reaching the sentence that matters, and the diagnosis says how much was dropped. OnTheHost piped its stderr for the same reason, so a death is diagnosed the same way whichever spawner started the process; its old Stdio::null() was avoiding the undrained-pipe hang that no longer exists.

Both death paths carry it, and that is a race rather than two cases: a server that dies before the request is written fails with EPIPE in send, one that dies just after fails as a disconnected channel in request_once, and which happens is timing. One of the two carrying the epitaph would have been a coin toss.

Nothing else moves. No syscall was added, no policy, no grant, no confinement, no fallback, no verify stage — the audit trail says this was not a seccomp kill, and guessing at a cause before the machine has been asked is what this change exists to stop.

Proven here with a stand-in that dies the way the property needs (rule 8): SIGSYS from a shell is not an approximation of a seccomp kill, it is the same signal. Five tests cover the signal, the status, the message surviving, silence being stated as silence, and a megabyte of log neither blocking nor being quoted. End to end through thalyx session, a renombrar-simbolo against a provider that dies now answers:

rust-analyzer did not answer: initialize: the server stopped — the
process was killed by signal 31 (SIGSYS); its stderr, 45 bytes: error:
could not find the sysroot; giving up

Claude-Session: https://claude.ai/code/session_01WtoE5f1628XjnDcoCWCUy3

On Fedora on 2026-08-30 stage 58 reported `analyzer_starts=1` and then
`rust-analyzer did not answer: initialize: the server stopped`, and there
was nothing else. `ausearch -m SECCOMP` over the exact seconds of the run
showed no SECCOMP, no AVC, no SELinux denial and no other kill. So the
machine held exactly one sentence about the death, and that sentence is
rule 10's failure shape: it says the reading failed and nothing about what
happened. A process killed by the seccomp filter, a process that could not
find its toolchain, and a process that panicked all close a pipe, and from
`request_once` all three were the same event.

Two things the kernel already knew were being thrown away.

**How the process ended.** `Analyzer` holds the `Child` and never asked it.
`epitaph` waits half a second — the pipe closing and the process being
reaped are two events in that order, and asking at the instant of the first
answers "still running" for a process that has already died — and then says
the exit status, or the signal and its name. `SIGSYS` is 31, and 31 on its
own tells the person reading nothing; a process killed by the filter dies
of exactly that signal, so if that is what happened on Fedora the next run
will say so in the refusal itself.

**What it wrote on the way out.** `launch::spawn` has always given a
confined program a `stderr` pipe and nothing on this path ever read it —
which is not only lost evidence but a latent hang, because a pipe whose
reader never empties it blocks the writer on a full buffer, and a server
stopped mid-indexing looks exactly like one that hung. It is drained
continuously now into a 4 KiB tail: the *last* bytes, because a server that
logs its way through an indexing pass would otherwise fill any buffer with
progress before reaching the sentence that matters, and the diagnosis says
how much was dropped. `OnTheHost` piped its `stderr` for the same reason,
so a death is diagnosed the same way whichever spawner started the process;
its old `Stdio::null()` was avoiding the undrained-pipe hang that no longer
exists.

Both death paths carry it, and that is a race rather than two cases: a
server that dies before the request is written fails with `EPIPE` in
`send`, one that dies just after fails as a disconnected channel in
`request_once`, and which happens is timing. One of the two carrying the
epitaph would have been a coin toss.

Nothing else moves. No syscall was added, no policy, no grant, no
confinement, no fallback, no verify stage — the audit trail says this was
not a seccomp kill, and guessing at a cause before the machine has been
asked is what this change exists to stop.

Proven here with a stand-in that dies the way the property needs (rule 8):
`SIGSYS` from a shell is not an approximation of a seccomp kill, it is the
same signal. Five tests cover the signal, the status, the message
surviving, silence being stated as silence, and a megabyte of log neither
blocking nor being quoted. End to end through `thalyx session`, a
`renombrar-simbolo` against a provider that dies now answers:

  rust-analyzer did not answer: `initialize`: the server stopped — the
  process was killed by signal 31 (SIGSYS); its stderr, 45 bytes: error:
  could not find the sysroot; giving up

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtoE5f1628XjnDcoCWCUy3
@CesarManzoCode
CesarManzoCode merged commit bee3adc into main Aug 30, 2026
1 check failed
@CesarManzoCode
CesarManzoCode deleted the claude/rust-analyzer-death-diagnostics-2krdy2 branch September 5, 2026 03:01
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.

2 participants