Repository navigation
feat!: one error envelope and fixed error vocabulary for all native tools - #49
Merged
Merged
Conversation
…ools
All five native tools return CallToolResult with IsError = true on failure and a
single ToolError envelope { error, message, reason?, candidates?, filePath?,
diagnosticId?, detail? } with the fixed codes Invalid, NotFound, Ambiguous, Stale,
Unavailable and Faulted. get_fixes returns an object with the fixes array; its
no-match cases become NotFound with a reason (incl. ruleset vs pragma suppression).
Ambiguous carries candidates with key, title and provider; equivalence keys round
trip (null keys were advertised as "" but never matched). list_rules catches
exceptions and validates projectPath like analyze (closes #33). The empty project
list message is fixed.
BREAKING CHANGE: error codes renamed, get_fixes success shape changed, isError set.
Closes #41
Closes #33
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n match first, precise no-change message) Malformed projectPath/filePath/folderPath strings are reported as Invalid instead of Faulted. FindDiagnosticAsync tries an exact line/column match in both the live and the pragma-suppressed set before any same-line fallback, so a suppressed position is not answered with a neighbouring live diagnostic. apply_fix says when a matching key produced no change in the document. README and AGENTS.md note that a rolled-back apply_fix_all batch is a non-error result with applied false. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… FileNotInProject for document scope) The fix tools validate filePath like projectPath, so a malformed path is Invalid, and echo the normalized absolute path in every result. apply_fix_all in document scope reports a file outside the project as NotFound/FileNotInProject instead of zero occurrences. analyze puts the almcp text in the message as well as in detail. Test sources no longer contain literal NUL bytes. Doc comment on CodeFixInfo corrected. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…instead of zero occurrences) apply_fix_all checked for a fix provider and for ruleset suppression up front but not for a loaded analyzer that reports the rule, so a rule whose analyzer is not configured came back as a success with zero occurrences while get_fixes and apply_fix report NotFound/NoAnalyzerForRule. The three tools now agree. README and the tool description name the second exception. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
Summary
All five native tools (
analyze,list_rules,get_fixes,apply_fix,apply_fix_all) now report failures the same way: the MCP result hasisError: trueand its text block holds oneToolErrorenvelope{ error, message, reason?, candidates?, filePath?, diagnosticId?, detail? }with a fixed vocabulary. An agent can decide between "fix the call", "retry", "pick a candidate" and "stop" fromerrorandreasonalone instead of parsing prose.errorreasonInvalidNotFoundNoDiagnosticAtPosition→ re-runanalyze;NoFixForEquivalenceKey→ pick fromcandidates; otherwise stopNoFixProvider,FileNotInProject,NoAnalyzerForRule,SuppressedByRuleset,SuppressedByPragma,NoDiagnosticAtPosition,NoFixForDiagnostic,NoFixForEquivalenceKeyAmbiguouscandidateslists{ equivalenceKey, title, providerName }StaleUnavailableNoProxy,AlmcpNotFound,AlmcpNotReady,AlmcpCallFailedFaultedmessageUnsupportedEncoding,ReadFailed,WriteFailedfor writes;detail= exception typeAlso in this PR:
get_fixesreturns{ diagnosticId, filePath, line, column, fixes: [...] }with a never-emptyfixes; each former "empty array" case is aNotFoundwith a reason. Ruleset suppression is checked before analysis; pragma suppression is told apart from "no diagnostic here" because the SDK keeps pragma-suppressed diagnostics flaggedIsSuppressedrather than dropping them.""byget_fixesbut compared againstnullbyapply_fix/apply_fix_all, so such a fix could never be applied. Both sides now go through the same normalization, andcandidatescarry title and provider so no secondget_fixescall is needed.list_rulescatches exceptions (an unhandled exception used to become the SDK's generic error text) and validates an explicitprojectPathagainst the startup projects exactly likeanalyze(closes list_rules should reject an unknown projectPath like analyze does #33). The message for an empty project list no longer renders asstarted with: . Pass one of those.Invalidfor aprojectPaththat does not exist or holds noapp.json, instead of a rawFileNotFoundException.analyzedistinguishesNoProxy,AlmcpNotFound,AlmcpNotReadyandAlmcpCallFailed; almcp's own text goes intodetail.## Errorssection; AGENTS.md "Tool patterns" describes the envelope, theisErrorrule and theToolErrors/ToolResults/ProjectScopehelpers.Breaking changes
Pre-1.0, shipped as an alpha prerelease from
main.StaleFile→Stale;UnsupportedEncoding/ReadFailed→Faultedwithreason;NoFixFound/NoFixAvailable→NotFoundwithreason;AmbiguousFix+availableEquivalenceKeys→Ambiguous+candidates;InvalidScope/MissingFilePath/InvalidLimit/UnknownProject/NoProject→Invalid;ProxyUnavailable/ProxyCallFailed→Unavailablewithreason; raw exception type names →Faultedwithdetail.isError: true. Success results never set it. The full JSON stays in the text block.get_fixessuccess is an object, not a bare array;fixes[]entries are{ equivalenceKey, title, providerName }(nodiagnosticIdper entry).apply_fix_allwith a rule the project ruleset sets toNoneisNotFound/SuppressedByRulesetinstead of adiagnosticsFound: 0success. Zero genuine occurrences remain a success.list_rulesrejects an explicitprojectPaththat is not one of the startup projects (Invalid).apply_fix_all'sconflicts[].kindvalues are unchanged.Deviations from the issue text
isErroras out of scope; it is set here by decision.SuppressedByRulesetOrPragmais split intoSuppressedByRulesetandSuppressedByPragma.NoDiagnosticsForRuleis dropped: zero occurrences inapply_fix_allstays a success (ruleset-suppressed excepted).NoFixForDiagnostic(keepsfixesnever empty on success) andNoFixForEquivalenceKey(withcandidates).FixCandidateis folded intoCodeFixInfo, sofixes[]andcandidates[]are the same type by construction.ToolErrorsbuildsCallToolResult, not strings.Verification
dotnet build --configuration Releaseanddotnet test --configuration Releasepass locally with the almcp-backed tests running (not skipped).NotFoundandUnavailablereason, everyInvalidsource,Ambiguouswith candidates,Stale,Faulted(exception and each write kind) and the key round trip throughget_fixes→apply_fixandapply_fix_all→apply_fix_allare covered by tests.ToolErrorsTestspins the envelope key order and that null fields are omitted.tests/.../Fixtures/PragmaProject/exerciseSuppressedByPragmawith a control page.Review
Three rounds of
code-review(high) on the branch before opening the PR. Confirmed findings were fixed in the two follow-up commits; the rest are listed here for the record.Fixed (confirmed)
projectPath/filePath/folderPathstrings (empty, embedded NUL, unsupported format) surfaced asFaultedwithSystem.ArgumentExceptioninstead ofInvalid. All five tools now normalize caller paths through one helper and returnInvalid.filePathat all (round 2).\0escapes.Adopted from the plausible list
FindDiagnosticAsynctries an exact line/column match in both the live and the pragma-suppressed set before any same-line fallback, so a request at a suppressed column is not answered with a live neighbour on the same line.apply_fixsays when a key advertised byget_fixesmatched but produced no change in the document (the fix may edit another file, which is Fewer round trips for fixes: optional equivalenceKey, dryRun with diff, multi-document changes written #43's scope).filePathin every result;apply_fix_allin document scope reports a file outside the project asNotFound/FileNotInProjectinstead of zero occurrences.analyzeputs almcp's text inmessageas well asdetailforAlmcpCallFailed.Carried, not changed (design calls)
apply_fix_allbatch is a non-error result (applied: false, failure inmessage, every staged file inconflicts), because the per-file outcome lives in the body and a partial skip is also a success withconflicts. Documented in README and AGENTS.md. A reviewer suggestedFaultedwhen nothing was written; open for discussion.Faultedexposesex.Messageand the exception type indetail. Chosen on purpose; the server is local stdio.IsSuppresseddiagnostic is described as pragma-suppressed. AL has only#pragma warning disable; the wording can be widened if the SDK adds other suppression sources.get_fixeslists every registered action whilecandidatesare de-duplicated by key (first wins). Same element type, documented onCodeFixInfoand in README.CollectDiagnosticsForRuleAsync/FindRemainingDiagnosticsAsyncstill filter by ruleset, which is redundant now thatapply_fix_allchecks it up front; left for Code-fix pipeline robustness: surface analyzer crashes, bound analyzer time, cache analysis per compilation #44, which owns those blocks.FixAllContextstill receives""for a null-key action; aFixAllProvidercomparing againstnullmay decline and fall back to the per-document path, which now matches correctly. No ALCops provider in the fixtures has a null key.Round 3 (final, whole branch): no confirmed findings. One plausible item was fixed in the last commit because it is an inconsistency inside this PR's own contract:
apply_fix_allreported zero occurrences for a rule no loaded analyzer reports, whileget_fixesandapply_fixsayNotFound/NoAnalyzerForRule; all three now agree. That commit was reviewed by the author only. Remaining round-3 items, carried:NotFound/Ambiguousenvelopes have no warning field.reason: NoFixForDiagnosticwith a specific message rather than a separate reason; revisit with Fewer round trips for fixes: optional equivalenceKey, dryRun with diff, multi-document changes written #43 (multi-document fixes), which removes the case.apply_fixregisters every provider's actions before matching the key, so a throwing unrelated provider faults the call.mainbehaved the same (providers were iterated in order until a match); Code-fix pipeline robustness: surface analyzer crashes, bound analyzer time, cache analysis per compilation #44 (analyzer and provider faults) owns this.Related
analyze's timing-dependentProxyUnavailablevsProxyCallFailed(the pre-check stays becauseIsAvailableis immutable andReadynever regresses; it is what yields the distinctAlmcpNotFound/AlmcpNotReadyreasons). The other analyze follow-ups: parser robustness and small cleanups #35 items stay open.🤖 Generated with Claude Code