fix(reports): persist fallback error artifact when report execute() throws (swamp-club#365)#1394
Conversation
…hrows (swamp-club#365)
There was a problem hiding this comment.
Code Review
Clean, well-scoped PR. The approach of persisting fallback error artifacts rather than silently discarding output is sound. The advisory semantics (no workflow failure, no exit code change) are correctly preserved throughout. DDD alignment is good — buildReportErrorResult is a pure domain function producing a value object (ReportResult), correctly placed in builtin/ alongside other built-in report logic.
Blocking Issues
None.
Suggestions
-
Duplicate imports —
src/domain/reports/builtin/report_error_report.ts:20-21has two separateimport typelines from"../report.ts". Could be combined intoimport type { ReportResult, ReportScope } from "../report.ts";. Minor style nit. -
Silent
catchin fallback persistence —src/domain/reports/report_execution_service.tsline ~449 has} catch {that silently swallows errors when the fallback artifact itself fails to persist. The design doc acknowledges this is intentional, but adebug-level log here (consistent with therunFailedcatch blocks inmethod_report_runner.ts:313andworkflow_report_runner.ts:163) would make failures more diagnosable without masking the original error.
Both are non-blocking. Test coverage is thorough — unit tests for the builder function, integration tests for the executeReports error fallback path including data handle propagation and callback wiring. License headers present on new files. Import boundaries respected (domain-internal imports only, presentation renderers import from libswamp/mod.ts).
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
-
Warning message could include the data key —
src/presentation/renderers/model_method_run.ts:158andworkflow_run.ts:173now emit"Report {reportName} failed, fell back to default error report: {error}". This is already much better than the old→ ✗format, but the message doesn't tell users where the fallback data lives. Something like"…fell back to default error report — retrieve with: swamp data get report-{name}-json: {error}"would make the warning immediately actionable without needing to consult docs. -
JSON mode silently drops report failures —
JsonModelMethodRunRendererandJsonWorkflowRunRendererboth havereport_failed: () => {}. This is pre-existing behavior, not introduced here, but the new fallback data handles are now surfaced in log mode via the warning while JSON mode gets nothing. A follow-up could expose the error artifact names in JSON output when report failures occur.
Verdict
PASS — the warning message improvement is clear and accurate, the fallback artifact is correctly persisted and documented, and the behavioral change (exit code / workflow status unchanged) is consistent with advisory-report semantics.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
-
src/presentation/renderers/model_method_run.ts:158andworkflow_run.ts:173— log message claims fallback succeeded even when it didn't. The warn message reads"Report {reportName} failed, fell back to default error report: {error}"unconditionally, regardless of whether the fallback artifact was actually persisted. Inreport_execution_service.ts:449, the innercatchsilently absorbs persistence failures, leavingerrorDataHandlesasundefined. The renderer has no way to distinguish "fallback persisted" from "fallback also failed" — it always claims the fallback happened. This produces misleading diagnostics when persistence is broken (e.g., disk full). Suggested fix: either split into two log messages (one for fallback-succeeded, one for fallback-also-failed) by includingdataHandleson thereport_failedevent, or soften the message to"Report {reportName} failed (error artifact may have been persisted): {error}". -
src/domain/reports/report_execution_service.ts:431-451— partial artifact persistence on the success→catch transition. Ifreport.execute()succeeds butpersistReportData(line 399) throws after writing the markdown artifact but before writing the JSON artifact, we enter the catch block. The fallbackpersistReportDatacall (line 439) then overwrites the markdown with error content, which is correct. However, if the fallback persist also fails (caught at line 449), the success-path markdown artifact remains in the data store while the result sayssuccess: falsewith nodataHandles. A consumer queryingreport-{name}by name would find the successful markdown content despite the result reporting failure. This is a very unlikely double-failure scenario but worth documenting in the design doc.
Low
-
src/domain/workflows/method_report_runner.ts:255—runFailedpath discards error fallback data handles. The_dataHandlesparameter is deliberately ignored, andrunFailedreturns[]. The artifacts are still persisted to the data store and discoverable by name, so this is functionally correct. But it means error fallback artifacts from reports-on-a-failed-method won't appear in the step'sDataArtifactRef[]list, while error fallback artifacts from reports-on-a-succeeded-method (line 150) will. This asymmetry is a deliberate design choice per the comments, but worth noting for future maintainers. -
src/domain/reports/builtin/report_error_report.ts:35— rawerrorMessageinterpolated into markdown. If the error message contains markdown syntax (headings, links, code fences), it will be interpreted as markdown structure. This is purely cosmetic since the content is rendered in a terminal, not a browser, and error messages are internal strings — but a multi-line error with---could produce unexpected horizontal rules in the rendered output.
Verdict
PASS. The core logic is correct: error fallback artifacts are persisted, the workflow status and exit code are unaffected, the onReportFailed signature change is backwards-compatible (optional parameter), and all three code paths (workflow runner, method runner success, method runner failure) handle the new data handles appropriately. Tests cover the key scenarios. The medium findings are edge cases in unlikely failure paths, not correctness bugs in normal operation.
Summary
execute()throws, swamp now persists a built-in fallback error artifact (markdown + JSON) instead of silently discarding the outputswamp data get report-<name>-jsonwith{ error: true, reportName, scope, message }Closes swamp-club#365
Test plan
deno checkpassesdeno lintpassesdeno fmt --checkpassesdeno run test— 5959 passed, 0 faileddeno run compilesucceedsreport-<name>-jsonswamp data get --workflow <wf> report-<name>-jsonreturns{ error: true, ... }🤖 Generated with Claude Code