Repository navigation
Conversation
explain_change_impact and detect_changes waited up to 3 s on every pending reindex receipt in the repository and then failed with "refused a stale graph". The edit that triggered the wait was usually in a file unrelated to the answer, and an id that did not resolve widened the wait to every receipt, so concurrent agents got refusals for most impact calls and each one held a dispatch slot for the full wait. Both handlers now wait at most 250 ms, and only for the edits that can change the answer: the queried symbols' own files, files with edges into them, files whose current bytes name them as a whole identifier (a call the graph has not read yet), and for detect_changes the changed files of the diff, which is now mapped files-first so the scope is known before the wait. An unresolved id contributes the file and name its spelling carries instead of widening the scope. Whatever in scope is still pending or failed labels the answer (complete:false, graph_freshness, freshness_scope, pending_files, pending_file_count, and recovery_hint when failed); the walk always runs. An earlier wait_deadline ends the wait sooner, including one that passed between admission and the handler reading it. TOON and plain text answers lead with a "relations: <state> (N files)" line, and GCX keeps GCX1 as line 1 with the label as header meta and the line as a comment on line 2. Every answer reports barrier_wait_ms. Only require_fresh refuses, once its wait_deadline passes with the scope still lagging. The label lives in relation_freshness.go so the relation-reading mutators can share it.
…lations The Stop briefing and the Codex PostToolUse briefing call detect_changes with only the 5 s transport bound, and fold every failure into an empty string. While edits were pending the change tool refused, the hook could not decode the error, and the agent was told nothing about its change set. detect_changes now gets 300 ms through callServerToolWithin, which tells a reply that did not arrive in time apart from an unreachable bridge. The call carries a wait_deadline 150 ms out, so the daemon labels the edits it is still publishing instead of waiting 250 ms for them on top of the diff and the walk, which would land the labelled answer past the budget. An empty reply after that deadline, which is how the daemon refuses a deadline that passed before it took the call up, counts as a stall. A stall, or an answer labelled pending or failed (graph_freshness on a primary checkout, graph_status on a worktree), yields a short briefing with the relations line and the changed files instead of silence or diagnostics over lagging relations. A stall says only that detect_changes did not answer, and stays silent for a session that recorded no writes, so a slow daemon does not put a warning into every read-only turn. renderDeadCodeHits never reports a symbol listed on a "pending_unknown " line of analyze dead_code as dead.
…allers detect_changes scoped its wait and label to the diff's changed files, but its risk and blast radius come from walking the changed symbols' callers. With scope staged or compare the working tree is not in the diff, so a pending edit in a caller file left the walk reading the old relations without a label. After the symbol join it now also waits for, and labels, the relation scope of the changed symbols: their files, the files with edges into them and the files that name them. That wait ends at the same deadline as the changed files' wait, so the answer still waits at most once.
…lers change_contract, preview_edit and safe_delete_symbol read callers and references from the graph and act on them or give a verdict, with no word about edits the graph has not published yet. Once edit acknowledgements stop waiting for the reindex, those windows become routine and the tools would answer from the old graph without saying so; safe_delete could delete a symbol whose only caller was added a moment ago in another file. Each tool now snapshots the unpublished edits before it walks, and once the walk has named the symbols involved labels the answer with the in-scope ones (graph_freshness, freshness_scope, pending_files, pending_file_count), leading TOON and plain text with the relations line. There is no default wait. safe_delete_symbol also scans the files of those edits for lines that name the target as a whole identifier and reports each one as a provisional reference, so the delete is rejected through the existing rejected_has_references status, and in propagate mode as a manual patch that blocks the propagation. An edit to the target's own file is one provisional reference for the whole file: it may add a caller there, and the graph's span of the target, which the delete cuts by, may no longer hold. A line the graph already holds a reference for is left to the graph, so it is neither counted twice nor turned into a manual patch. With cascade:"apply" every closure member gets the same scan, since the closure is orphaned by the graph's edges only, and a hit refuses the cascade. The scan stands in for the unresolved call stubs of a pending projection until those exist. preview_edit maps the touched files to graph keys before scoping the label, so a file named by an absolute URI still counts.
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.
Status: work in progress.
perf/scoped-change-freshness), and mcp: label change answers instead of refusing on pending edits #1228 must land first. Only the last commit, c60eb82, is new here. Once mcp: label change answers instead of refusing on pending edits #1228 merges, this branch will be rebased and the diff will shrink to that commit.change_contract,preview_editandsafe_delete_symbolare done. The handlers in files that feat: measure edit-loop phases and add edit-and-probe diagnostics #1169 also changes are not done yet.change_contract,preview_editandsafe_delete_symbolread callers and references from the graph, then act on them or give a verdict. They say nothing about edits the graph has not published yet. Once edit acknowledgements stop waiting for the reindex, unpublished edits become routine, and these tools would answer from the old graph without saying so. Worse,safe_delete_symbolcould delete a symbol whose only caller was added a moment ago in another file.Changes (commit c60eb82)
graph_freshness,freshness_scope,pending_filesandpending_file_count. TOON and plain text start with the relations line from mcp: label change answers instead of refusing on pending edits #1228. These tools never wait by default and never refuse.safe_delete_symbol. It scans the files of those edits for lines that name the target as a whole identifier, and reports each line as a reference of kindprovisional. This has two effects:rejected_has_referencesstatus.propagate, each line becomes a manual patch, which blocks the propagation (propagation_blocked_manual).cascade:"apply"scans every closure member the same way, because the graph's edges alone decide which members are orphaned. Any hit refuses the cascade, whateverforcesays.preview_editmaps the touched files to graph keys before it scopes the label, so a file named by an absolute URI still counts.Status
Done:
change_contract,preview_editandsafe_delete_symbol.Not done. These live in
tools_enhancements.goandtools_coding.go, which #1169 also changes, so they wait for it:verify_changemust never answer a bare "clean:" while relations are pending.analyze dead_codemust list pending-unknown symbols in every mode. The hook in mcp: label change answers instead of refusing on pending edits #1228 already drops symbols listed on apending_unknownline.rename_symbolmust report provisional sites and never write a file whose own edit is pending.get_edit_planneeds the labels.Not included: #1218, the stale-line fix for propagate deletes (also part of #1019). It merges cleanly with this branch.
No test on this branch is red by design.
Tests
On the base, the new tests fail: the target is deleted, or the answer carries no label.
TestSafeDeleteRejectsTargetCalledFromPendingFilehas these subtests:In every case the delete is rejected or the propagation is blocked, and the files stay byte-identical.
TestRelationToolsLabelAndNeverRefuseWithUnresolvedReceiptcoverschange_contract,preview_edit(including a file URI) andsafe_delete_symbol.Review fixes each came with a test that failed first. Examples: the "definition file" case deleted the target; a graph reference was counted twice; the cascade member was deleted; and
preview_editwith a file URI carried nograph_freshness.At this head, under
-race, these pass: the mcp change-tool set plusSafeDelete,Propagate,ChangeContract,PreviewEditandSimulat.Static checks:
go vetandgofmtare clean. golangci-lint over./internal/mcp/...reports 0 issues.Untested: the full package suites. CI runs them.
Refs #1019