Repository navigation
Commit e779def
fix(server): close the tool_call record when output_sanitisation blocks a response (#1687)
## Summary
When `output_sanitisation` blocks a response (`response_action: block`),
the call_tool_* handlers returned before recording the call's
completion. The upstream call **did** run (side effects may have
happened), yet only a `policy_decision` was persisted: no `tool_call`
activity row (started events are not persisted), an SSE `started` event
with no completion, and no tool-call history entry.
`handleCallToolVariant`, the legacy `handleCallTool` and direct routing
mode (`makeDirectModeHandler`, added by #1685) now close the call on a
block:
- **Tool-call history:** the attempt is stored with `Error` set to the
block message and `Response` cleared, so the blocked payload is never
stored.
- **Activity:** a `tool_call` completion is emitted with status `error`,
`error_class: proxy_policy` and `fault_domain: proxy`. It carries the
fixed block message, never the response. On call_tool_* paths the
`internal_tool_call` record is emitted as well.
- The new helper `recordOutputBlocked` follows the convention of the
code_execution sub-call block path, which already closed its record this
way.
## Counting / audit check
- **Spec 107 audit:** the block's `policy_decision` already writes
`tool_call outcome:blocked reason:output_sanitisation`
(`isPostDispatchBlockKey`). The completion's audit write is the existing
deduped no-op (`toolCallWritten`), so the audit log still has one line
per attempt.
- **Telemetry:** nothing is double-counted. The availability block
counter comes only from `policy_decision`, the upstream-attempt bucket
was counted at dispatch, and activation is not recorded.
- **Activity/Usage call totals (`storage.CountsAsCall`):** the
`policy_decision` (blocked) and the new `tool_call` (error) both count
as failed calls. One blocked attempt therefore counts as two failed
calls in those totals. The code_execution sanitisation block and the
generation-changed refusal pair already counted this way. Separating
them needs the post-dispatch block key on the `policy_decision` record,
so that is left for a follow-up.
## Direct mode
After #1685 merged, origin/main was merged into this branch and the
direct-mode block now emits the same error `tool_call` completion.
Direct mode keeps no tool-call history.
## Tests
- `TestCallToolVariant_SanitisationBlockRecordsToolCallCompletion` and
`TestLegacyCallTool_SanitisationBlockRecordsToolCallCompletion` drive a
real in-process upstream that returns an AWS key with block mode on.
Each asserts one persisted `tool_call` with status `error` and
`error_class` `proxy_policy`, the `policy_decision` beside it, and the
secret absent from every activity record.
`TestDirectMode_SanitisationBlockRecordsToolCallCompletion` asserts the
same for direct mode. All three failed before the fix (no `tool_call`
record).
- `TestRecordOutputBlocked_HistoryCarriesBlockMessageNotResponse`
asserts the history entry carries the block message and no response. The
direct-routing fixture's incarnation guard drops all history writes, so
this is tested on the helper.
- `go test -race ./internal/server/` with the unit-tests.yml `-skip`
regex: pass.
- `golangci-lint` v2 (`.github/.golangci.yml`), bare and `--build-tags
server`, over `./internal/server/...`: no issues in changed files. Six
existing staticcheck findings remain in untouched test files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>1 parent ab0574c commit e779def
3 files changed
Lines changed: 179 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3564 | 3564 | | |
3565 | 3565 | | |
3566 | 3566 | | |
| 3567 | + | |
| 3568 | + | |
| 3569 | + | |
| 3570 | + | |
| 3571 | + | |
| 3572 | + | |
| 3573 | + | |
| 3574 | + | |
| 3575 | + | |
| 3576 | + | |
3567 | 3577 | | |
3568 | 3578 | | |
3569 | 3579 | | |
| |||
4054 | 4064 | | |
4055 | 4065 | | |
4056 | 4066 | | |
| 4067 | + | |
| 4068 | + | |
| 4069 | + | |
4057 | 4070 | | |
4058 | 4071 | | |
4059 | 4072 | | |
| |||
4129 | 4142 | | |
4130 | 4143 | | |
4131 | 4144 | | |
| 4145 | + | |
| 4146 | + | |
| 4147 | + | |
| 4148 | + | |
| 4149 | + | |
| 4150 | + | |
| 4151 | + | |
| 4152 | + | |
| 4153 | + | |
| 4154 | + | |
| 4155 | + | |
| 4156 | + | |
| 4157 | + | |
| 4158 | + | |
| 4159 | + | |
| 4160 | + | |
| 4161 | + | |
| 4162 | + | |
4132 | 4163 | | |
4133 | 4164 | | |
4134 | 4165 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
745 | 745 | | |
746 | 746 | | |
747 | 747 | | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
748 | 753 | | |
749 | 754 | | |
750 | 755 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
0 commit comments