fix(harness): classify OpenCode v2 errors and retain snapshots
Summary
Follow up on the merged OpenCode V2 adapter (#3367) with typed 404 handling and changed-file timeline preservation, adapted to current main after #3456.
Changes
- Decode V2
_tagerrors on session routes, including message history. OnlySessionNotFoundErrormeans the session is unavailable; missing commands, models, locations, permissions, and forms retain execution-error diagnostics. Malformed or oversized error bodies cannot establish session absence. - Preserve typed V2 errors during resumed-session setup and reconciliation, while retaining unavailable/waiting handling for host outages and missing sessions. Regression tests check that setup errors do not interrupt or replace user-supplied sessions.
- Normalize assistant
snapshot.filesinto a stable patch event. Append it after usage normalization so a snapshot appearing during refresh does not duplicate the usage timeline event. Repeated refresh tests verify updated files, stable sequence/timestamp, and no redundant progress update. - Extend real OpenCode 2.1.4 validation with an unknown command, a real file write and snapshot, and missing-session responses. Existing question/resume and cancellation-settlement checks remain in that test.
- Link managed OpenCode sessions from the README action table.
Scope boundary
Command timeout policy is deliberately not changed. The old handoff removed the HTTP timeout using the caller context; current upstream instead uses context.WithoutCancel to learn the outcome of potentially accepted submissions. Removing its remaining bound would require a separate decision. Admission, deduplication, interruption, and settlement logic from #3456 is not replaced.
A separate temporary diagnostic against the new patch and OpenCode 2.1.4 confirms the remaining gap: a command containing a 31-second shell substitution returned after 30.568 seconds with unavailable, PromptSent=true, and zero provider calls. This diagnostic is not part of the submitted patch. The preexisting command-admission failure path's unconditional interrupt also remains outside this PR's scope.
Validation
New regressions were run before their fixes and failed for typed 404 classification, snapshot omission, resumed-host routing, swallowed history errors, malformed trailing JSON, and snapshot/usage timeline duplication.
Verified on Linux with Go 1.27.3 and OpenCode 2.1.4, using isolated temporary state and a local deterministic HTTP provider:
go test -race -count=1 ./internal/runtime/builtin/harness/... ./internal/opencodehost/...
go test -race -run '^TestV2' -count=20 ./internal/runtime/builtin/harness/
DAGU_TEST_OPENCODE_V2_EXECUTABLE=/path/to/opencode-2.1.4 \
go test -race -run '^TestRealV2Run$' -count=2 ./internal/runtime/builtin/harness/
go vet ./internal/runtime/builtin/harness/... ./internal/opencodehost/...
go fix -embedlit=false -diff ./internal/runtime/builtin/harness/... ./internal/opencodehost/...
The affected package race suite passed; the real-V2 test passed twice; scoped vet/fix and formatting checks produced no findings. Repository-configured golangci-lint 2.13.2 reported zero issues. Independent review findings were addressed and re-reviewed.
Validation limitations: directory-boundary-corrected lint exposes the same 22 findings on untouched upstream and this patch; the changed-lines comparison reports zero new issues (see #3465). Windows/Darwin lint attempts exceeded three-minute package-loading budgets. Full make fmt/make check, repository-wide Go tests, frontend/API suites, and conformance were not run; this is a package-scoped harness change with no schema, frontend, workflow-language, or CLI changes.
Related Issues
Closes #3467 (tracking issue for the fixes in this PR).
Replaces the remaining applicable work from philo-x/dagu#1. Related: #3367, #3456, and the lint-exclusion follow-up #3465. This does not reopen or claim to resolve the closed cancellation issue #3448.
Checklist
- Code follows the project style guidelines
- Self-review of the code has been performed
- Tests have been added or updated as needed
- Documentation has been updated as needed
- Changes have been tested locally
Summary by cubic
Classifies OpenCode v2 HTTP 404 responses so a session is marked unavailable only when the error is SessionNotFoundError. Missing commands, models, locations, permissions, and forms keep execution-error diagnostics instead, and typed errors survive resume and history reconciliation.
- A run that cannot reach the host before it has a session now fails immediately, so the step can fall back to the CLI or retry; only resumed sessions wait for the host to return.
- History polling fails a step only on typed 404s naming a missing resource; transient errors such as 5xx or unreadable pages retry on the next poll as with v1 hosts.
Assistant snapshot.files are normalized into a stable patch timeline event appended after usage normalization, so a snapshot appearing during refresh does not duplicate the usage event. Regression tests cover 404 classification, malformed and trailing error bodies, host-down and resume routing, transient vs. typed history errors, and snapshot/usage timeline stability. Real OpenCode 2.1.4 validation now includes an unknown command, a real file write with snapshot, and missing-session responses, and the README action table links managed OpenCode sessions via a relative doc link. Command timeout and admission/interruption policy are unchanged.
Summary by CodeRabbit
- New Features
- Assistant messages now include file changes from content snapshots as patches.
- Bug Fixes
- Managed OpenCode sessions now report unavailable or missing sessions more clearly, while preserving other session and history errors.
- Failed message refreshes on newer OpenCode sessions now surface their errors.
- Documentation
- The
harness.runaction description now links to documentation for managed OpenCode sessions.
- The