Repository navigation
Conversation
The registry starts empty on every boot, so until its own warmup job registered it a configured repository read as untracked: MCP answered repo_not_tracked with "run gortex track", initialize handed out the INACTIVE track-then-reconnect instructions, the CLI said "add it with gortex track", and the hook could deny native reads from persisted rows. Record each configured repository's load state (queued, running, ready, failed, missing) in a lock-free table on MultiIndexer, announced before checkout seeding and settled by every warmup job. The admission gate, the server instructions, the control probe, file coverage and the CLI read it: a loading repository gets a retryable repo_loading with progress, a failed or missing one gets repo_unavailable with a reason, and a path outside every configured root is still repo_not_tracked. When a repository first registers, cached session scopes are dropped, a repo_state readiness event and a per-session message go out, and the checkout coordinators that were skipped for an unregistered primary start, so sessions work in place with no reconnect.
zzet
force-pushed
the
fix/startup-repo-loading
branch
from
October 24, 2026 08:44
8782e59 to
2211f27
Compare
The track subtest pasted the configured root into the JSON-RPC frame as is. On Windows the path holds backslashes (D:\a\_temp\...), which are invalid JSON escapes, so the gate could not parse the call and answered repo_loading instead of already_configured. Encode the path with json.Marshal.
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.
Summary
After every daemon restart, a configured repository read as untracked until its own warmup job registered it. That takes about a minute cold, about 10 s warm with a couple of hundred worktrees, and minutes on large stores. In that window:
repo_not_trackedwith "Rungortex track <cwd>".initializehanded out the INACTIVE "track, then reconnect" instructions.gortex track", or "rungortex repos reconcile" from a worktree.Agents re-tracked, stopped, or deadlocked. This is the most likely shared cause of reports that the MCP catalog was unavailable for an hour after an upgrade.
With this PR, configured repositories read as loading and get a retryable answer. Every load ends in a terminal state, and sessions start working in place when their repository registers.
Changes
Per-repository load state (
internal/indexer/repo_load_state.go)MultiIndexer:queued → running → ready | failed{reason} | missing, keyed by path identity (lexical and symlink-resolved).Global().Repos) before checkout seeding, so the seed window no longer reads as untracked. Each job records start and finish. A batch that fails before draining settles the leftovers, so no repository reads as loading after the work that would have loaded it is gone.failedcovers a failed cold track or reconcile, a recovered panic, and a job that ended without registering (includingprefix_taken).missingcovers a configured path that does not exist.PendingRepoForPathmatches a cwd inside a configured root, above one, or in a linked worktree of a loading primary (by git common dir, without spawning git). It reads no store and takes nomi.mu.MCP admission (
cmd/gortex/daemon_mcp*.go,internal/mcp/repo_loading.go)tools/callreturnsrepo_loadingwithretryable:true,phase,percent,repoandrepo_path. The suggestion never mentions tracking.workspace(operation:"index")counts asindex_health. It admits:index_health, answered from the load record and listing only the caller's repo;capabilitiesandtools_search;subscribe_*/unsubscribe_*;resources/read,prompts/get, graph reads,graph_statsand mutations returnrepo_loading.failed/missingreturnrepo_unavailable(retryable:false, withreason). Capabilities and track still answer, because tracking again is the repair.initialize/server/discover: the server picks the instructions, so they stay in step with the gate.track_repository/index_repository/workspace_admin(track)for a still-loading configured root return{status:"already_configured", state:"loading"}instead of queueing behind warmup.repo_not_tracked.Refresh on registration
multi.gocold track and warm reconcile) mark the repository ready and fire a registration event. Re-index and file-save rewrites of an existing entry do not fire it, so edits don't clear scopes.InvalidateSessionScopes();workspace_readiness {event:"repo_state", repo, state:"ready"}without replacing the warmup phase;notifications/messageto each session whoseinitializereported the repository as loading;CheckoutLifecycle.PrimaryRegistered(prefix).sessionScopeno longer caches the blind sentinel for a loading cwd.PrimaryRegisteredstarts the coordinators that were skipped because their primary was unregistered.ensureCoordinator, so the order of the family reconcile and the registration doesn't matter.Close.ProbeRepogainsstate(loading|ready|failed|missing) andstate_reason, read from the lock-free table, so the no-lock, no-store contract holds.CLI
repo_loading→ "the gortex daemon is still loading (phase, N%)", never "track" or "reconcile". It is a typed error with exit code 75 (EX_TEMPFAIL).--loading-wait(default 30 s). Off a TTY (hooks, scripts) it fails fast. When the wait runs out it reports "still loading", never a failure of the work.--loading-waitbecausegortex trackalready defines a boolean--wait.repo_loading) instead of printing "rungortex repos reconcile".Hooks
ControlFileCoveragereport indexed files as covered while MCP refuses graph calls. That is the same no-read-path shape as PreToolUse hook blocks native Read/Edit while Gortex refuses writes for the same checkout, leaving no edit path #932.FileCoverageResult/DirCoverageResultnow carryrepo_stateandrepo. A loading repository is never reported as indexed, so native Read, Grep, Bashcatand Edit are allowed, with one line of context. The deny resumes atready.Docs and comments
docs/mcp.md;docs/multi-repo.mdupdated.daemon.gocomment that promised serving from the persisted store, the stale tools/list comments indaemon_mcp.go, and theprobeCWDReachcomment.Not in this PR (deliberately)
repo_loadingrather thanrepo_not_tracked. Admitting it before warmup can be cancelled per repository would let warmup re-register the repository and re-add its config entry after an accepted untrack.IndexAll/IndexScoped/indexMultiRepo): tests still useIndexScoped, so it's left for a follow-up.Testing
go test -race ./...), run locally (macOS):go test -race ./...: every other package passes.internal/graph/store_sqlite,internal/indexerandinternal/mcphit the 10-minute default timeout, the reason CI shards them.internal/indexer: all six CI shards pass under-race, using the patterns fromscripts/ci-test-partition.py --dry-run(678–1344 s each).internal/mcppasses under-racewith a 55-minute timeout (1387 s).cmd/gortex,internal/hooks,internal/daemonandinternal/pathkeypass under-race.internal/graph/store_sqliteis untouched and was not rerun past its timeout.gofmt -sandgo vetare clean.golangci-lint(local v2.15.0) is clean on the changed files; it flags 3 errcheck hits oncase <-errChan:in untouched tests (internal/daemon/session_reconnect_test.go,internal/indexer/checkout_deferred_retirement_ready_restart_test.go), a check newer than CI's pinned v2.14.0.EvalSymlinks)New tests:
internal/indexer/repo_load_state_test.go:PendingRepoForPathfor a root, a subdirectory, a parent and a linked worktree;mi.muis held;PrimaryRegisteredstarts a coordinator skipped on restart, while another repo's registration (or registration alone) starts nothing.cmd/gortex/daemon_mcp_repo_loading_test.go:resources/read,prompts/get,graph_stats,index_healthlegacy and facade (scoped to the caller),capabilities, track →already_configured,initialize,tools/list;repo_unavailable;repo_not_tracked;notifications/messageand no reconnect.internal/mcp/repo_loading_test.go: the sentinel scope is not cached while loading; loading and unavailable instructions; facade → legacy resolution.internal/hooks/repo_loading_test.go: loading coverage is never indexed; Read, Edit and Bashcatare allowed; the Grep scope is allowed; the deny resumes when ready.cmd/gortex/daemon_repo_loading_test.go:repo_state;Checklist