Skip to content

aft_inspect: passing scope makes the call slower, not faster — the parameter reads as a cost reduction and is the opposite #247

Description

@iceteaSA

scope is documented as narrowing output without reducing work. In practice it does something stronger and in the costly direction: passing a scope switches the diagnostics category off the cheap path onto the expensive one. Agents scope by reflex believing it is the considerate call, and it is the single most expensive request in the tool surface.

The branch

inspect/diagnostics_category.rs:86-102:

let collection = if applicability_is_empty {
    // ...
} else if scope_was_provided {
    // A deferred inspection can collect diagnostics for the entire root
    // without an explicit scope. Scope filters rendered results, not work.
    match collect_scoped_diagnostics(ctx, snapshot, scope) { /* ... */ }
} else {
    collect_warm_working_set(ctx, snapshot)
};
  • no scopecollect_warm_working_set — the warm set only
  • with scopecollect_scoped_diagnostics — per-file collection that opens/ensures a language server per file at up to PULL_FILE_TIMEOUT (10s) each, bounded only by the 120s phase budget

commands/inspect.rs:142 feeds the flag straight in as scope_was_provided || force_root_diagnostics.

The in-code comment is accurate about work not shrinking. The gap is that the parameter doesn't merely fail to help — it selects a different, slower collection strategy.

sections has the related-but-milder version of the problem: commands/inspect.rs:130 iterates InspectCategory::active() regardless, so every category is verified no matter what the caller asks to render. Net effect for a caller: there is no argument that makes aft_inspect cheaper. The only lever is not calling it.

Why the cost lands on other sessions

inspect maps to Lane::SerialLspStatus (subc/manifest.rs:124-131) and holds lsp_inflight, a term in has_epoch_reader, which gates Lane::Mutating (executor/mod.rs:2075-2079). Set once at admission (:2118), cleared once in complete_job (:1894).

So an expensive inspect is not just slow for its caller — it blocks every other session's writes on that root for its whole duration. Production pairing (from #243):

holder:  name=inspect      channel=832 corr=157  exec=77185ms
victim:  name=apply_patch  channel=833 corr=159  queue=71208ms exec=36ms

A 77-second inspect; a sibling's apply_patch waited 71.2s in queue and then ran in 36ms. That inspect was scope=src, sections=all — the exact shape this issue is about.

What the caller sees

aft_inspect [scope=src, sections=all, topK=20]
  route closed by closeRoute
aft_outline [target=tests, files=true]
$ git status --short && git rev-parse --short HEAD
  request on local_port=56346 channel 60 corr 1241 timed out after 18000ms

The follow-on timeouts are subsequent calls aging out behind the inspect. Nothing in that surface suggests scope caused it.

Suggestions, cheapest first

  1. Fix the description. "Use scope= to narrow returned results. It does not reduce the fresh verification work" reads as cost-neutral. Something like "scope narrows rendered output; it also forces per-file diagnostics collection and is more expensive than an unscoped call" would stop the reflex.
  2. Separate the two intents. Output narrowing and per-file diagnostic collection are different asks currently fused into one parameter. A caller who wants "show me only src/" almost never means "and collect diagnostics file-by-file across it."
  3. Default the render filter to the cheap path — let scope narrow output while diagnostics stay on the warm-working-set path unless the caller explicitly opts into scoped collection.

(1) alone would have prevented every instance we've hit.

Minor, unrelated: collect_warm_working_set carries #[allow(dead_code)] at diagnostics_category.rs:115 but is live at :101.

Happy to send a PR for (1) if the wording is all you want.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions