The agent turns every provider error except provider.ErrUnsupported into a plain string (internal/agent/handler.go:296-315, response.Error = err.Error()). About 30 controller handlers then choose HTTP status codes with strings.Contains(errMsg, "not found").
This has already broken in places:
internal/controller/api/node/sysctl/sysctl_update.go:75 returns 500 on any error, so updating a missing key returns 500 instead of the 404 the spec declares, while sysctl_get.go and sysctl_delete.go check for not-found.
- The not-found branch in
internal/controller/api/agent/agent_drain.go:59 and agent_undrain.go:63 can never match, because WriteAgentTimelineEvent never returns such an error.
Fix: add sentinel errors in internal/provider beside ErrUnsupported (for example ErrNotFound), carry an error code in job.Response alongside the message, classify with errors.Is in the agent, and switch on the code in controllers.
Found in the September 2026 codebase review. Tracked in the review tracking issue.
The agent turns every provider error except
provider.ErrUnsupportedinto a plain string (internal/agent/handler.go:296-315,response.Error = err.Error()). About 30 controller handlers then choose HTTP status codes withstrings.Contains(errMsg, "not found").This has already broken in places:
internal/controller/api/node/sysctl/sysctl_update.go:75returns 500 on any error, so updating a missing key returns 500 instead of the 404 the spec declares, whilesysctl_get.goandsysctl_delete.gocheck for not-found.internal/controller/api/agent/agent_drain.go:59andagent_undrain.go:63can never match, becauseWriteAgentTimelineEventnever returns such an error.Fix: add sentinel errors in
internal/providerbesideErrUnsupported(for exampleErrNotFound), carry an error code injob.Responsealongside the message, classify witherrors.Isin the agent, and switch on the code in controllers.Found in the September 2026 codebase review. Tracked in the review tracking issue.