Repository navigation
Description
Why
Sometimes the daemon starts warming a repository the user did not mean to track, such as a home directory, a monorepo parent or a huge vendored tree. That warmup can run for many minutes or hours and keep the machine busy. We do not yet promise that untracking the repo works while the daemon is still warming it. The supported way out is:
- stop the daemon;
- remove the repo from the config while the daemon is down;
- start the daemon again.
Today no test covers this path from start to finish, and no doc describes it.
Stop itself is slow right now. Every gortex daemon stop is force-killed after about 18 s: a 15 s exit grace plus up to 15 s for the checkpoint on close. A separate shutdown issue sets the stop budget:
- ≤3 s when idle;
- ≤5 s after a cold index or after churn;
- 0 SIGKILLs in 20 runs;
- the store is closed and its lock released.
This issue holds the escape hatch to that same budget. The v0.64.12 upgrade note will tell users: "to stop a runaway warmup: gortex daemon stop, then offline gortex untrack". This issue makes that sentence true and keeps it true.
Current state
Verified on main:
- Stop (
cmd/gortex/daemon.go,runDaemonStop):- It records a sticky stop intent (
daemon.MarkStopIntent), so an editor'sgortex mcpproxy does not respawn the daemon. - When systemd or launchd owns the daemon (
serviceActive()), it stops it throughserviceStop. Otherwise it callsstopRunningDaemon, which waits for the process to exit and release the store lock. daemonStopExitGracesets how long it waits before a force-kill: 15 s after an ack, 2 min with no ack, 5 min at most.
- It records a sticky stop intent (
- Untrack (
cmd/gortex/track.go,runUntrack):- It asks
untrackDaemonIndexwhether a running daemon owns the target (daemon.IsRunning()plusdaemonOwnsRepoincmd/gortex/cli_daemon.go). - If no daemon owns it, it falls back to editing the config only:
config.LoadGlobal→RemoveRepo→Save. It prints[gortex] untracked <path> (config only)and does not open the store.
- It asks
- Gotcha: untrack by prefix needs the daemon.
RemoveRepo(internal/config/global.go) makes its argument absolute and matches it by path identity. A bare repo prefix such asgortex untrack backend, which the docs show, therefore resolves against the current directory when the daemon is down. It fails withrepository not foundunless the user runs it from the repo's parent. Offline untrack only works reliably with a path. - Next start (
cmd/gortex/daemon_state.go): warmup runs an orphan-prefix purge first (orphan_maintenance, thenorphan_prefix_scan, thenorphan_prefix_purge). It purges every stored repo prefix that no tracked repo in the config maps to. A repo removed from the config while the daemon was down therefore has its rows purged on the next start instead of being indexed. - Tests:
cmd/gortex/track_offline_test.gocovers only offlinetrack(TestTrack_OfflineSucceeds,TestTrack_OfflineIdempotent). No test covers offlineuntrack. No test covers stop → untrack → start against a real daemon in mid-warmup. - Docs:
docs/cli.mdsays track and untrack fall back "to config-only when no daemon" is running.docs/onboarding.mdhas a Troubleshooting section, but it says nothing about recovering from a runaway warmup.
What to do
- Gated end-to-end test. Skip it unless an opt-in env var is set (for example
GORTEX_E2E=1), so the defaultgo test ./...stays fast. The test:- builds or reuses the
gortexbinary and runs it against an isolatedHOME, config, socket and store; - tracks a small fixture repo A and a large synthetic repo B, then starts the daemon. B must be big enough that its cold warmup is still running when the test acts;
- waits until
gortex daemon statusshows warmup of B in progress, then runsgortex daemon stopand records its wall-clock time; - checks that the process exited without SIGKILL, the socket is gone and the store lock is released (a fresh store open succeeds);
- runs
gortex untrack <absolute path of B>and checks that it took the config-only path, exited 0, removed B from the global config and kept A; - starts the daemon again and checks that A becomes ready, B is neither warmed nor indexed, and B's repo prefix has no rows left after the orphan purge;
- logs each stop duration and whether a SIGKILL happened, so a repeated run can be audited.
- builds or reuses the
- Unit tests for the offline
runUntrackpath, next to the existing offline-track tests, with no daemon running:- removing a tracked path deletes that entry and keeps the others;
- removing an unknown path returns a clear
repository not founderror and leaves the config unchanged; - a bare prefix given from a directory where it does not resolve returns the same clear error and does not delete some other entry.
- Docs. Add a short "Recovering from a runaway warmup" section to the Troubleshooting part of
docs/onboarding.md, and link it fromdocs/cli.md. It covers:gortex daemon stop, and that it goes through the service manager when one is installed;gortex untrack <path>with the daemon down, using a path rather than a bare prefix;gortex daemon start;- what to expect: the repo's rows are purged on the next start;
- how to confirm, with
gortex daemon status.
- Upgrade note. Use the same steps and wording in the v0.64.12 upgrade note.
Out of scope
- Making stop itself fast. The separate shutdown issue owns that, and the stop-timing targets below can only pass once it is merged.
- Supporting
untrackagainst a daemon that is still warming the target. - Making offline
untrackaccept a bare repo prefix (it is documented and tested as a clear error here, not fixed). - Windows service-manager specifics beyond what the existing stop path already does.
Done when
- The gated end-to-end test exists and is skipped when the opt-in env var is unset.
- With the env var set, the test passes on macOS and Linux in 20 consecutive runs (
-count=20). - In those runs,
gortex daemon stopduring an active cold warmup returns in ≤5 s wall clock every time. There are 0 SIGKILLs across all 20 runs, and the store lock is released before the next step. - Offline
gortex untrack <path>returns in ≤1 s, exits 0 and removes only the target entry from the global config. - After the restart:
- the untracked repo is not warmed and
gortex daemon statusdoes not list it; - its repo prefix has 0 rows in the store;
- the other repo reaches ready.
- the untracked repo is not warmed and
- Unit tests for offline
runUntrackcover the three cases above (tracked path, unknown path, unresolvable bare prefix) and pass undergo test -race ./cmd/gortex/. - The troubleshooting section is merged in
docs/onboarding.mdwith a link fromdocs/cli.md, and the v0.64.12 upgrade note uses the same steps. - The gated test is part of the release-gate run on the reference Linux host and the reference Mac.
How to verify
go test -race -run 'Untrack.*Offline' ./cmd/gortex/GORTEX_E2E=1 go test -run TestOfflineUntrackEscapeHatch -count=20 ./cmd/gortex/, then read the logged stop durations and SIGKILL count. Run this on macOS and on Linux.- By hand:
- Track a large directory and run
gortex daemon start. - Wait for warmup to begin, then run
time gortex daemon stop. - Run
gortex untrack /abs/path/to/dir, thengortex daemon start. - Run
gortex daemon status: the directory is gone and its rows were purged (look for theorphan_prefix_purgestep in the daemon log).
- Track a large directory and run
Dependencies
Blocked by (must land first):
- Make
gortex daemon stopfinish in seconds without SIGKILL: tolerant teardown, bounded close #1022 — The offline-untrack test assumes daemon stop finishes within the stop budget.
Related issues
- Related: Catalog primary graph keeps a stale repo_prefix: worktrees never get a coordinator, untrack fails forever with "cannot serve checkout … yet" #898, daemon: deferred passes lost when a cold warmup dies after the bulk flush — next start sees repos unchanged and never re-arms them #670, The daemon cannot be stopped by 'gortex daemon stop' as expected #68
Planning
- Milestone: v0.64.12
- Priority: enabler
- Estimate: ~0.5 engineer-day
- Isolated enough for a community contributor.
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
gortex daemon stopfinish in seconds without SIGKILL: tolerant teardown, bounded close #1022zzet commentedon Oct 28, 2026
Implementation is prepared in an isolated worktree at
40565f6a6, based on the shutdown work in #1022.Source review is complete; automated checks and the native repetitions are still pending. The issue remains In Progress and is not accepted. #1022 must land first. Validation is queued behind the current #1005 checks so only one heavy local job runs at a time.
zzet commentedon Oct 28, 2026
Native acceptance for #1029 now passes on both platforms at
d55ae9c9e616c1f5947bb0d116db20e863f8c25fin #1172:Each platform also passed a separate pilot. The test verified active cold indexing before stop, released store locks, config-only removal of the absolute path, the retained repository reaching ready, and zero removed-prefix rows after restart. Independent cleanup checks found no owned processes, sockets or handles left behind. Linux ran under an 8-CPU/16-GiB cgroup with swap disabled; its VM is now stopped.
Four focused race controls and all prescribed pinned local checks passed. Raw logs, artifact hashes and cleanup receipts are saved locally. Administrative setup failures are retained separately and were not counted as native runs.
The issue remains In Progress: hosted CI and #1022 landing are still required. This result completes the native test requirement; it does not replace #1022's broader shutdown workload gates.
zzet commentedon Oct 29, 2026
The native escape-hatch gate also passes on the prepared integration with the latest shutdown implementation.
Test source:
517d4d146a781da3d4730e5698e4a4e588bca73a(currently local; PR push is held until #1022 lands). Non-test Go source matches the measured shutdown product at5e223d667e9eca8d23e07f51787ddf21883362f1; the Linux product retains that source attribution. Both platforms used freshly built race test binaries from the current test source.Every case observed active cold indexing, stopped normally within 5 seconds, released the store lock, removed only the requested absolute path through the offline config-only command within 1 second, and restarted with the remaining repository ready and the removed prefix purged. Actual test/child processes, fixture/socket paths and handles were checked after completion. The Linux VM has been stopped normally.
The first Linux compile attempt failed before any product execution because an owned copy inherited read-only cache directory permissions. Only that owned copy was corrected; the failed preflight evidence is retained. There were no failed or retried native acceptance cases.
Linux raw evidence archive SHA-256:
6b06d6152c6264c079ca763b1c7eb6d71dcc765967c25b05f902e8d7032057b8; current Linux race test binary:80920e4b784dbd6c81d5115c17966a5879873217947fbd0867af82930e00606d. Local evidence is retained under the external SSD'sgortex/validation/v06412-1029/directory. Earlier results atd55ae9c9remain historical observations, not relabeled current-source measurements.Remaining: #1022 must merge; then retarget #1172 to main, incorporate any final dependency test fixes, run the prescribed checks before pushing, and pass the standard affected CI. The issue remains In Progress until those steps complete.
zzet commentedon Oct 29, 2026
Accepted after PR #1172 passed its required CI and merged, following prerequisite #1022.
The opt-in escape-hatch test passed 20 consecutive native cases on macOS and 20 on Linux, covering 80 normal daemon exits with zero forced kills. Maximum complete stop was 301.858 ms on macOS and 175.393 ms on Linux (5-second limit); maximum offline untrack was 41.767 ms and 28.139 ms respectively (1-second limit). Every case checked active cold indexing, store-lock release, config-only removal of the absolute target path, the remaining repository becoming ready and removed-prefix cleanup after restart.
Offline unit controls, affected race tests and the pinned lint/format/build checks passed. Native evidence retains source
517d4d146and the Linux product's5e223d667attribution; final integratione6a8b4b799changes only a tested upstream fixture. Earlier preflight failures remain recorded separately. The CLI, onboarding, upgrade and release-gate documentation now carry the recovery procedure.The issue is closed and moved to Done. Detailed native evidence.