Skip to content

change.detect can exceed MCP deadline in multi-repo workspace; summary_only is not count-only #691

Description

@tiendungdev

Summary

change(operation="detect") can exceed the MCP facade's ~59-second deadline in a multi-repo workspace even with output.summary_only=true. Narrowing the same call with options.repo makes it complete in 10–34 seconds.

This is not an index-health failure. The measured workspace reported health_score=100, clean path liveness, and a ready daemon.

Environment

  • Gortex: v0.63.8+2a33f3d
  • Host: Windows 11
  • MCP client: Codex 0.150.1
  • Workspace: 4 near-identical linked worktrees/repositories
  • Daemon snapshot: 10,004 files, 535,510 nodes, 2,808,466 edges
  • Connected MCP sessions at snapshot: 22
  • Daemon state: ready; memory 291 MiB; 111 goroutines

No source, repository content, or raw pprof output is attached.

Reproduction

Broad workspace call:

{
  "operation": "detect",
  "source": {"scope": "all"},
  "output": {"summary_only": true}
}

Observed after approximately 59 seconds:

tool "change" exceeded its 58.997s deadline and was abandoned

Repo-scoped calls against the same daemon and dirty tree:

{
  "operation": "detect",
  "source": {"scope": "all"},
  "options": {"repo": "domain-manage"},
  "output": {"summary_only": true}
}

Completed in 10.309 seconds.

Changing only the scope to unstaged completed in 34.037 seconds. The variance is consistent with concurrent/background contention, but both repo-scoped calls stayed within the MCP deadline.

Per-repo controls:

  • clean linked worktree: 0.48 seconds;
  • dirty documentation worktree: 10.67 seconds;
  • another clean linked worktree: 0.46 seconds.

analyze(kind="retrieval_log", options:{repo:"domain-manage"}) reported for the latest 5,000 queries:

change n=423 p50=238.4ms p95=143003.7ms

The p95 is over twice the MCP deadline.

Index/daemon controls

workspace(operation="index") completed in milliseconds and reported:

health_score: 100
path_liveness.clean: true
orphan_files: 0
resolution_regressions: 0
edges_ok: true

gortex daemon status reported the daemon as ready. A graph-wide analyze health and workspace graph call also exceeded the same deadline, which points to broad graph/store work or contention rather than a git diff failure specific to change.detect.

All detect scopes reported that they read tracked git diff only. Untracked .codex-worktrees directories were therefore not the cause.

summary_only observation

Successful responses with summary_only=true still contained the complete changed_symbols, changed_files, and file_changes payloads. The option removed per-depth row lists but did not avoid:

  • Git diff collection;
  • range-to-symbol lowering;
  • reach traversal;
  • materialization/serialization of changed symbols.

Therefore summary_only is useful for response shaping but does not currently provide a count-only fast path.

Retry behavior

The timeout response says work may continue in the background. Retrying the same broad request immediately three times can therefore stack expensive work and amplify contention. A later repo-scoped request succeeds, so restarting/reindexing a healthy daemon is not an appropriate first response.

Requested improvements

  1. Current-repo default or hook scoping

    • Default change.detect to the current repo when a session cwd resolves unambiguously, or make the Codex post-edit hook pass the explicit repo selector.
    • Preserve an explicit workspace-wide option for callers that need it.
  2. True count-only output

    • Add an output mode that avoids materializing changed_symbols and per-symbol reach rows when only counts/risk/file names are required.
    • Alternatively clarify that summary_only is response-only and add a separate compute-short-circuit field.
  3. Deadline cancellation

    • Propagate the MCP context cancellation through Git diff/lowering/reach work so an abandoned request does not continue consuming graph/store capacity.
  4. Phase timing diagnostics

    • Return or locally log bounded timings such as git_diff_ms, lower_ms, reach_ms, serialize_ms, selected repo count, seed-symbol count, and whether the reach index fell back to a live graph walk.

Current workaround

After the first timeout, do not retry the broad call immediately. Check index health, then retry once with both an explicit options.repo and the narrowest required source.scope. If that still times out, use already-known symbol IDs for targeted impact/tests/guards/contracts and record the detect timeout rather than claiming full-workspace absence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions