Skip to content

Commit 73eff25

Browse files
ipasechnikovclaude
andcommitted
Tighten check-errors approval gate; richer list-errors summaries
Make skill explicit that ready-to-run commands still need user approval, even in auto mode. Require markdown-table summary on step 1. list-errors.sh: collapse HTTP 422 OperationOutcome into a short `HTTP <code> <kind> [schema] <expression> — <diagnostics>` line and render unmappedCodes directly for code_mapping_error rows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1c5bcdd commit 73eff25

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

.claude/skills/check-errors/SKILL.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ description: Check recent HL7v2 processing errors in Aidbox, diagnose root cause
55

66
# Check HL7v2 Processing Errors
77

8-
Diagnose and help resolve `IncomingHL7v2Message` errors from the HL7v2→FHIR pipeline. Work iteratively — summary first, one error at a time, never auto-fix without approval.
8+
Diagnose and help resolve `IncomingHL7v2Message` errors from the HL7v2→FHIR pipeline. Work iteratively — summary first, one error at a time.
9+
10+
**MANDATORY APPROVAL GATE.** Never run any fix command without explicit user approval, even in auto mode and even when the inspect script prints a "ready-to-run" line. "Ready-to-run" means *ready for the user to approve*, not *ready for you to execute*. Present the proposed command, wait for an explicit "yes" / "do it" / "go ahead", then run. This applies to `wire-preprocessor.ts`, `defer.sh`, `resolve-mapping.sh`, `verify-retry.sh`, `mark-for-retry`, config edits, and any other state-changing action. Read-only inspection (`list-errors.sh`, `triage.sh`, `inspect-error.sh`, `status.sh`, `loinc-search.sh`, `list-preprocessors.sh`, `check-message-support.ts`) does not require approval.
911

1012
## Step 1: Summary
1113

@@ -27,6 +29,8 @@ Prints each error with a suggested class (auto-swap / fhir-422 / sender-missing
2729

2830
Ask: **"Which error would you like me to investigate?"** Skip `deferred` rows unless the user explicitly asks.
2931

32+
**Present the summary back to the user as a markdown table** (not a bullet list), even when condensing or rewording the script output. Columns: `#`, `Type`, short `Summary`, `ID`.
33+
3034
## Step 2: Inspect one
3135

3236
```sh
@@ -35,7 +39,7 @@ scripts/errors/inspect-error.sh <id>
3539

3640
Emits: status, type, sender, full error, unmapped codes (if present), raw HL7v2 saved to `/tmp/hl7v2-<id>.hl7`, and an `hl7v2-inspect` overview for `parsing_error`/`conversion_error`. **You do not need to curl the resource yourself.**
3741

38-
For `HTTP 422` conversion errors the script also prints the **current values** of each candidate HL7v2 field, so you typically don't need an additional `hl7v2-inspect --field` call. For `per-1` (reversed period) it also emits a **ready-to-run** `wire-preprocessor.ts` command — just copy/run it. For `code_mapping_error` with any `observation-code-loinc` task, peer OBX rows are dumped automatically AND LOINC candidates are fetched via ValueSet/$expand on each localDisplay — you usually have everything needed to pick the right LOINC without another call.
42+
For `HTTP 422` conversion errors the script also prints the **current values** of each candidate HL7v2 field, so you typically don't need an additional `hl7v2-inspect --field` call. For `per-1` (reversed period) it also emits a **ready-to-run** `wire-preprocessor.ts` command — present it to the user for approval, do NOT run it yourself. For `code_mapping_error` with any `observation-code-loinc` task, peer OBX rows are dumped automatically AND LOINC candidates are fetched via ValueSet/$expand on each localDisplay — you usually have everything needed to pick the right LOINC without another call.
3943

4044
Pick the playbook below by the `Status` line from Step 2.
4145

@@ -148,7 +152,7 @@ scripts/errors/status.sh <id>
148152
## Rules
149153
150154
- Summary first, then one error at a time.
151-
- Never auto-fix without approval.
155+
- **Never run a fix command without explicit user approval** — applies even in auto mode, even when a "ready-to-run" command is printed, even when the fix seems obvious. Propose → wait for "yes" → run. Covers `wire-preprocessor.ts`, `defer.sh`, `resolve-mapping.sh`, `verify-retry.sh`, `mark-for-retry`, and any config edit.
152156
- Skip `deferred` rows in the summary unless the user asks about them.
153157
- Don't hand-count pipes — the inspect script already ran `hl7v2-inspect.sh`. For deeper field lookup use `scripts/hl7v2-inspect.sh --field SEG.N`.
154158
- Use the `hl7v2-info` skill to verify HL7v2 spec compliance when needed.

scripts/errors/list-errors.sh

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ fi
3030

3131
AIDBOX=http://localhost:8080
3232
ERROR_STATUSES="parsing_error,conversion_error,code_mapping_error,sending_error"
33-
ELEMENTS="id,status,type,error,sendingApplication,sendingFacility,meta"
33+
ELEMENTS="id,status,type,error,sendingApplication,sendingFacility,unmappedCodes,meta"
3434

3535
fetch() {
3636
local status_filter="$1"
@@ -43,6 +43,33 @@ render_table() {
4343
jq -r '
4444
def trunc($n): if . == null then "" else gsub("[\r\n]+"; " ") | .[0:$n] end;
4545
def sender($r): ($r.sendingApplication // "") + "/" + ($r.sendingFacility // "");
46+
# Collapse a raw error message into a short, human summary.
47+
# Handles: plain text, "HTTP N: {OperationOutcome json}", and the
48+
# "Sending failed (attempt x/y): ..." retry prefix.
49+
def summarize_error:
50+
if . == null then ""
51+
else
52+
gsub("[\r\n]+"; " ")
53+
| sub("^Sending failed \\(attempt [0-9]+/[0-9]+\\): "; "")
54+
| . as $e
55+
| ((capture("^HTTP (?<s>[0-9]+): (?<b>\\{.*\\})$")? // null)) as $m
56+
| if $m == null then $e
57+
else
58+
($m.b | fromjson?) as $oo
59+
| if $oo == null then $e
60+
else
61+
($oo.issue[0] // {}) as $i
62+
| ([ $i.details.coding[]? | select(.system? | test("operation-outcome-type")) | .code ][0] // $i.code // "error") as $kind
63+
| ([ $i.details.coding[]? | select(.system? | test("schema-id")) | .code ][0] // "") as $schema
64+
| ($i.expression[0] // "") as $expr
65+
| ($i.diagnostics // "") as $diag
66+
| ("HTTP " + $m.s + " " + $kind
67+
+ (if $schema != "" then " [" + $schema + "]" else "" end)
68+
+ (if $expr != "" then " " + $expr else "" end)
69+
+ (if $diag != "" then " — " + $diag else "" end))
70+
end
71+
end
72+
end;
4673
if (.entry // []) | length == 0 then
4774
empty
4875
else
@@ -52,7 +79,15 @@ render_table() {
5279
| to_entries[]
5380
| .key as $i
5481
| .value.resource as $r
55-
| "| \($i + 1) | \($r.status // "") | \($r.type // "") | \(sender($r)) | \($r.error | trunc(80)) | \($r.id) |"
82+
| (if $r.status == "code_mapping_error" and (($r.unmappedCodes // []) | length) > 0 then
83+
($r.unmappedCodes
84+
| map("\(.localCode)\(if .localDisplay then " (" + .localDisplay + ")" else "" end)")
85+
| join(", ")
86+
| "Unmapped codes: " + .)
87+
else
88+
($r.error | summarize_error)
89+
end) as $summary
90+
| "| \($i + 1) | \($r.status // "") | \($r.type // "") | \(sender($r)) | \($summary | trunc(160)) | \($r.id) |"
5691
)
5792
end
5893
'

0 commit comments

Comments
 (0)