Skip to content

fix(daemon): reject projectless watch roots - #559

Open
HashemKhalifa wants to merge 1 commit into
ScriptedAlchemy:masterfrom
HashemKhalifa:fix/session-git-discovery-cpu
Open

fix(daemon): reject projectless watch roots#559
HashemKhalifa wants to merge 1 commit into
ScriptedAlchemy:masterfrom
HashemKhalifa:fix/session-git-discovery-cpu

Conversation

@HashemKhalifa

Copy link
Copy Markdown
Contributor

Summary

  • keep discovery-mode tracedecay serve projectless when the current directory is neither an initialized project nor an auto-init Git root
  • seed daemon Git watchers only for registered roots that are initialized Git checkouts
  • add regressions for projectless discovery and stale non-Git registry rows

Why

Starting serve from a non-project directory could promote that directory into daemon project routing. Once recorded, daemon startup could also seed a Git watcher for the arbitrary directory, causing unnecessary discovery and indexing work.

This change preserves projectless discovery until a real project root is available and prevents stale registry metadata from admitting non-Git or uninitialized watch roots.

Tests

  • cargo test --lib daemon::git_watch::tests
  • cargo test --lib serve::tests
  • cargo test --test daemon_suite git_watch
  • cargo test --test mcp_suite no_explicit_path
  • cargo test --test mcp_suite explicit_initialized_path_ignores_initialize_roots
  • cargo test --test mcp_suite serve_with_reachable_daemon_proxies_before_opening_explicit_project
  • npm run lint:commit -- --from upstream/master --to HEAD

@changeset-bot

changeset-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 95d194a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@HashemKhalifa
HashemKhalifa marked this pull request as ready for review August 20, 2026 15:37
@HashemKhalifa

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95d194ab1a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/serve.rs
Comment on lines +195 to +198
let auto_init_candidate = project_path.as_deref().or(original_cwd);
let auto_init_root = auto_init_candidate
.filter(|candidate| !initialized && crate::config::load_sync_config(candidate).auto_init)
.and_then(crate::worktree::git_worktree_root);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check auto-init config at the Git root

When discovery-mode serve starts in a subdirectory of an uninitialized Git repository, this filter loads configuration from that subdirectory before git_worktree_root resolves the repository root. A root-level .tracedecay/config.json with sync.auto_init=false is therefore missed, the default true is used, and the daemon is allowed to initialize a repository whose configuration explicitly opts out. Resolve the Git root first and evaluate its sync configuration before setting allow_init.

Useful? React with 👍 / 👎.

Comment thread src/serve.rs
Comment on lines +206 to +209
let telemetry_timings = timings
|| project_path
.as_deref()
.is_some_and(|path| crate::config::load_telemetry_config(path).timings);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve timings after initialize-root routing

For a projectless CWD that is later routed to a real project through MCP initialize.roots, this expression permanently sets the handshake's timings flag to false unless --timings was passed. apply_daemon_initialize_route updates only the project path and allow_init, while run_daemon_connection_with_timings passes this false value as an override; consequently the routed project's telemetry.timings setting—including its default true—can never enable response timing annotations for this flow. The routed project configuration needs to be applied after initialize-root resolution rather than treating the initially absent path as an opt-out.

Useful? React with 👍 / 👎.

Comment thread src/daemon/git_watch.rs
Comment on lines +319 to +321
if root.is_dir()
&& root.join(".git").exists()
&& TraceDecay::has_initialized_store_with_options(&root, &open_options).await

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the watcher cap after rejecting stale rows

code_projects_seen_within(window, cap) applies its SQL LIMIT before these new validity checks, so recent stale or projectless rows still consume the entire watcher budget. If the newest watch_max_projects rows are invalid, initialized Git projects that are only slightly older are never examined or watched after daemon startup, even though no watcher slots were actually used. Fetch enough candidates to fill the cap after filtering, or move equivalent validity filtering ahead of the limit.

Useful? React with 👍 / 👎.

Comment thread src/serve.rs
Comment on lines 188 to 190
} else {
crate::config::resolve_path_with_discovery(None)
original_cwd.and_then(crate::config::discover_project_root)
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain registry-only initialized CWD routing

Using only synchronous filesystem discovery makes an initialized profile-sharded project invisible when its local enrollment marker is missing but its store remains resolvable through the global registry—a supported state handled by discover_project_root_with_identity. For a non-Git project, or a Git project with TRACEDECAY_SYNC_AUTO_INIT=false, auto_init_root cannot restore the path, so a no---path client now sends a projectless handshake and project tools fail unless that MCP host happens to provide usable initialize roots. Previously the CWD fallback was still sent to the daemon, which could resolve the registered store; preserve a daemon-side identity lookup for this case rather than dropping the path outright.

Useful? React with 👍 / 👎.

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.

1 participant