Repository navigation
Conversation
The MCP server reads one request from stdin, awaits handle_request() for it and only then reads the next one. So a query that scans the whole database, grep_functions over every function body or find_implementors, holds up every other client of the server until it is done, and they all run into the idle timeout of the MCP client at the same time. Twenty review agents on one server stalled for the better part of an hour behind a few such scans, while the CLI, which runs the same query code in a process of its own, kept answering in under a second. So hand each request to a task of its own and let the responses go out as they finish. JSON-RPC matches them by id, the order doesn't matter, and the stdout lock already serializes the writes with the notification task. The handlers borrow their arguments across awaits in a way rustc can't prove Send for, so the task drives the request on a thread of the blocking pool with Handle::block_on() instead of running it on a worker. That also keeps a CPU bound scan off the runtime's workers. The number of requests in flight is capped at the number of CPUs with a semaphore, so that any number of scans can't thrash the machine. Notifications are still handled inline, there is nothing to wait for there. A task that can't write its response gives up on it and logs that, the loop ends on its own when stdin is closed. A find_function sent right after a grep_functions over all function bodies of a kernel tree used to be answered after the scan, 26 seconds in. Now it comes back in 4 seconds while the scan is still running. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
The server starts answering requests right away, but lookups only work once the startup pass has checked that the index covers the current commit, or has indexed it. Whoever starts the server has no way to tell when that is, other than asking and being told to try again. The systemd CI grew a probe for exactly that. So speak the sd_notify(3) protocol when NOTIFY_SOCKET is set: STATUS= lines for the phases, READY=1 once lookups are answered, STOPPING=1 on the way out. systemd-notify --fork then returns when the server is ready, and a Type=notify unit becomes active at that point. A failed check or indexing run sends a STATUS= line and no READY=1. No new dependency, it is a datagram to the socket, with the abstract form handled. Suggested-by: Daan De Meyer <daan@amutable.com> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Until the startup pass is done every lookup is answered with "currently being indexed, try again", even when the check finds nothing to do, which takes a second or so. A client that starts the server and asks right away, as an agent does, gets a refusal and has to know to retry. So let lookups wait for the startup pass, up to thirty seconds, and only then say that indexing is still running. The check itself is over long before that. A real indexing run of a stale index is not, and keeps the old answer. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
brauner
added a commit
to brauner/systemd
that referenced
this pull request
Nov 6, 2026
The probe hands the server its four requests, keeps the pipe open for three seconds and then reads whatever came back. That is too little in two ways. The first lookup on the kernel index takes a few seconds right after the index was restored, so the pipe closes before the answer arrives. And when the top commit of the pull request touches no C file, semcode-mcp cannot tell from the diff that its index is current and scans the whole index at startup, answering the lookup with "being indexed, try again" for about a second. Either way a working server is left out of the review as if it were broken, which happened to the kernel server on one review and to the checkout's server on the next. Instead of guessing from the outside when the server is ready, let it tell us. semcode-mcp now implements the sd_notify protocol (cf. [1]): it reports STATUS= while it checks the index against the current commit and READY=1 once the check is through and lookups are answered, and a lookup that arrives during the check waits for it instead of being refused. The pin moves to the fork commit that has this. The probe becomes a small Python script that does what systemd-notify --fork would do: bind a socket, start the wrapper with NOTIFY_SOCKET pointing at it and wait for READY=1, for up to thirty seconds. We can't use systemd-notify itself. The runner has systemd 255 and --fork came with 258, and it would connect the server's stdin to /dev/null, which ends a stdio server right away. The socket lives next to the wrapper because the sandbox mounts a tmpfs over /tmp and /run and would not see it there. The probe accepts READY=1 only together with a status that says the index is up to date, so a server that comes up with a stale index is left out as well, and a failed check or an exiting server fails the probe at once. The wrapper hands NOTIFY_SOCKET into the sandbox only when it is set. Claude Code sets none, so the servers it starts notify nobody. The script lives next to the workflow, like the build and unit test scripts do, instead of in a heredoc inside it. The review takes it from the base revision the same way it takes AGENTS.md, never from the checkout, which is the pull request's. [1]: masoncl/semcode-devel#11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
semcode-mcp answers requests right away, but lookups only work once the
startup pass has checked that the index covers the current commit, or has
indexed it. Until then every lookup gets "currently being indexed, try
again", even when the check finds nothing to do, and whoever started the
server has no way to tell when it is ready other than asking. The systemd
CI, which starts a server per review agent, grew a probe for exactly that.
Two commits on top of #10:
READY=1 once lookups are answered, STOPPING=1 on the way out, no READY=1
after a failed check. No new dependency, it is a datagram to the socket,
abstract form included. Daan De Meyer suggested this.
being sent away while it only checks. A real indexing run keeps the old
answer after that.
Tested with a listener on the socket: READY=1 after 0.1 s on a systemd
index, 0.2 s on a checkout whose top commit touches no C file (the case
that scans the whole index), 0.9 s on a kernel index; an empty index
reports the failed check and never says ready. A lookup fired at startup
on that checkout now waits about a second and answers instead of being
refused.
Note that systemd-notify --fork is not a way to probe a stdio server: it
wires the child's stdin to /dev/null, so the server quits at EOF, and
exits 0 on a clean child exit. A small NOTIFY_SOCKET listener is.