Skip to content

A long-lived process serves the repository as it is, never as it was when it started

A long-lived process serves the repository as it is, never as it was when it started

majordomus://rule/project.the-server-sees-the-current-tree@1active rustmcpserverfreshness

it references

referenced by

Rationale

On 2026-09-11, on origin/master at faba8fdef, a shared server was started, asked for the repository's head, given a commit, and asked again:

HEAD before   2e70f780ba5cddd9f42b4d139456645357d72fac
the API said  2e70f780ba5cddd9f42b4d139456645357d72fac
HEAD after    2c6f59557d650903ee582bdb80dd0b164ed4028c
the API said  2e70f780ba5cddd9f42b4d139456645357d72fac

The same stale answer came from MCP and from the Cockpit, a rule committed while the server ran was absent from objects, and a working tree reported clean while it was dirty. Nothing anywhere said the picture was old.

That is the most expensive shape a wrong answer can have. A session attached to this server for a day reads a repository that no longer exists, decides on it, and is never told — the answers are well-formed, confident and current-looking, and the only remedy is a restart nobody knows to perform. The server exists so that several workers share one reading of the repository; a shared reading that is hours out of date is worse than none, because each worker would otherwise have read the repository itself.

Required behaviour

crate::live::Live holds the repository a long-lived process reads. Every path that is about to use the context takes it from there, and taking it compares a crate::live::Stamp — the size and modification time of the git control files, about eight stat calls — with the one the current generation was built at. When they differ, the request that noticed rebuilds the layer; a second request arriving mid-rebuild has already seen the move, so it waits for that rebuild and is answered from the generation it produces rather than from the one known to be stale.

Everything that is a projection of the index is memoised per generation and not once per process: the OpenAPI document, the MCP tool and resource listings, the resolved web surfaces. Context::continuing carries what is not a projection of anything across the rebuild — the peer board, the running executions, the capability executor — because a reload is not a restart and a peer must not lose its place because somebody committed.

This refines project.rust-hot-path and does not loosen it. That rule's counters are about work a request repeats over a repository that has not changed, and they still do not move across any number of such requests. What this rule adds is that the work is done once when the repository has changed, by the request that noticed, rather than never.

A one-shot command, a benchmark and a test take Live::pinned and pay nothing: the picture cannot go stale inside a process that lives for 40 ms, and making them prove it per call would be a regression bought with nothing.

Failure behaviour

A layer that will not load after a move is not an error to the client: the last generation that did load goes on being served and the reason is logged. The failed load consumes no change: the generation keeps the stamp and the write count it was built at, and the failure is recorded beside it, so the same state is read again once RETRY_AFTER has passed — never on every request, never given up on — and a further move or write is read at once. A request that waited for a rebuild which then failed is answered the same way, from the generation that did load: it does not repeat the load it waited to see fail, so one failure costs one load however many requests queued behind it. A repository that is not a git work tree has no moves to follow and is pinned, which is said once in the log.

Verification

test/cases/150_server_sees_the_current_tree.sh: a server is started, its head recorded, a commit made, and the same process — not a restarted one — asked again through the HTTP API and through an MCP session opened before the commit. The object committed in that commit must be in the index, and the peer must still be on the board.

crate::live's own tests hold the check to the cost that makes it possible: a stamp is taken in well under a millisecond, because it runs before every request the server answers.

Where this comes from

canonical file
.ai/repo/rules/project/the-server-sees-the-current-tree.v1.md
identity
project.the-server-sees-the-current-tree@1
also served as
MCP resource majordomus://rule/project.the-server-sees-the-current-tree@1 · majordomus entity show rule/project-the-server-sees-the-current-tree-1 · GET /api/v1/entity?uri=majordomus://rule/project.the-server-sees-the-current-tree@1
in the Cockpit
/cockpit/objects/rule/project-the-server-sees-the-current-tree-1

This page is generated from the file above by scripts/generate-site-data; editing the page changes nothing, editing the file changes every surface. The relations above are edges of docs/generated/graph.json, which the executable writes from the same index.

Searches the titles and descriptions of every page this site publishes.