Say Did not happen about a call the audit page had been calling Allowed - #351
Merged
davidmckayv merged 2 commits intoSep 4, 2026
Merged
Conversation
The audit page falls back to "Allowed" for any row it does not recognise, which is right for the many rows that are neither a refusal nor a failure. Two rows that are failures were falling through to it. mcp.call_failed is written by callTool after the policy allowed the call: the vendor answered isError, or the attempt threw. It is the direct sibling of computer.action_failed, written by the same decide-record-then-act order on the other acting surface, and computer.action_failed was already in the family. Every failure of a per-person connector lands on this row - no connection for the asker, a refresh token the vendor stopped accepting, an API not enabled for the project - which makes it the most common failure this product has and the one row of the family drawn in the same muted colour as a call that worked. component.function_failed is a component's data read that was granted and then broke. server/src/audit.ts says it is "deliberately not a refusal", and that is right: nothing was forbidden. It is not an allowance either. The page already labels it "Could not be read" while colouring it as though it had been. Both are also absent from the Did not happen saved view, which is the half that is harder to notice: the view an administrator opens to ask what did not work here is not empty, it is just short by exactly the rows they came for. Neither is filed as a refusal, because nothing was forbidden on either row and filing a broken call as a policy event teaches a reader to distrust the policy events that are real. connector.sync_failed is declared in the same union and left alone on purpose: nothing writes it, the worker sync is not wired, so classifying a row that cannot exist would be a guess. It needs the same line the day it is wired. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
kevin9327
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso and
tylerslaton
as code owners
September 3, 2026 21:56
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
# Conflicts: # CHANGELOG.md
davidmckayv
approved these changes
Sep 4, 2026
davidmckayv
left a comment
Contributor
There was a problem hiding this comment.
Reviewed diff against current main; resolved CHANGELOG keep-both; validated locally (format + composed test run). CI green.
9 tasks
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.
What this changes
The audit page draws a row it does not recognise as
Allowed. That is right for the many rows thatare neither a refusal nor a failure — a credential saved, a person's role changed. Two rows that
are failures were falling through to it.
mcp.call_failed. Written bycallTool(server/src/plugins/store.ts:2957and:2992) afterthe policy allowed the call: the vendor answered
isError, or the attempt threw. It is the directsibling of
computer.action_failed— same decide, record, then act order, on the other actingsurface — and
computer.action_failedwas already in the family. Every failure of a per-personconnector lands on this row: no connection for the asker, a refresh token the vendor stopped
accepting, an API not enabled for the project. So this is the most common failure the product has,
and it was the one row of the family drawn in the same muted colour as a call that worked.
component.function_failed. Written atserver/src/components/routes.ts:278when a granteddata read throws.
server/src/audit.ts:279says it is "deliberately not a refusal", and that isright — nothing was forbidden. It is not an allowance either. The page already labels it
"Could not be read" while colouring it as though it had been.
Both were also absent from the
Did not happensaved view, which is the half that is harder tonotice: the view an administrator opens to ask what did not work here is not empty, it is just
short by exactly the rows they came for.
Neither is filed as a refusal. Nothing was forbidden on either row, and filing a broken call as a
policy event teaches a reader to distrust the policy events that are real.
This is the same drift
#302was about, on the two surfaces#302did not reach. The file's ownheader says the lists exist so "a new refusal is added in one place or in none" — that held for
refusals and not for failures.
Deliberately left alone:
connector.sync_failedmatches the same shape and is declared in thesame union, but nothing writes it — the worker sync is not wired — so classifying a row that cannot
exist would be a guess. It needs the same line the day it is wired.
Where it runs
literals in
app/src/lib/audit/outcome.ts.bundle; the rows it classifies were already written by the server, unchanged.
Did not happenview's?eventType=querygrows by two names; the audit read path already accepts a comma list (
server/src/audit.ts:515).Boundary and audit
only what the page says they mean, and nothing is reclassified as a refusal.
Changelog
A tool call that failed no longer reads as one that worked, underUnreleased.Proof
Fail before, pass after, confirmed by reverting the fix in
outcome.tsand re-running.With
mcp.call_failednot in the list:And with only
component.function_failedremoved:With the fix:
Gates: