Skip to content

docs: how a provider's error reads - #245

Merged
retr0h merged 1 commit into
mainfrom
docs/provider-error-shape
Oct 2, 2026
Merged

retr0h merged 1 commit into
mainfrom
docs/provider-error-shape

Conversation

@retr0h

@retr0h retr0h commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

One section in providers.md, one routing row in the skill. Settles osapi-io/osapi#565 so the mechanical fix has something to follow.

A provider's error reaches a job result, the audit log and a CLI user's screen. Nothing said what shape it takes, and the tree grew four:

sysctl, netplan     "sysctl create: ..."               <domain> <verb>
ntp, service        "ntp: ..."                         <domain>
certificate, cron   "create certificate: ..."          <verb> <domain>
file                "failed to execute template: ..."  failed to <verb>
user                "chpasswd failed: ..."             <command> failed

The shape

<domain> <verb>: what went wrong

sysctl create: key must not be empty
schedule delete: not managed by osapi
file deploy: execute template: no such template

sysctl and netplan already use it. It sorts and greps usefully, and reads the same whether the error arrives alone or wrapped three deep.

The two habits, and why each is wrong

An error does not announce that it failed. These are almost always wrapped, so failed to execute template renders as "deploy schedule entry: failed to execute template: ...". The words carry nothing the context did not already.

An error names the operation, not the command. chpasswd failed leaks the implementation into a message a CLI user reads, and stops being true the day the implementation changes. user set password survives that.

Not in scope

The four sentinels stay exactly as they are. They are compared with errors.Is rather than read, so the convention does not apply:

ErrUnsupported  ErrNotFound  ErrNotManaged  ErrNotInstalled

%w wrapping on any path carrying a cause stays mandatory, since an error that loses its cause cannot be matched, which is what the sentinels are for.

just test passes. All skill links and anchors resolve.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FuKUsHFG1EqZXamffh9M2c

A provider's error reaches a job result, the audit log and a CLI user's
screen, so its wording is an interface. Nothing said what shape it takes and
the tree grew four.

Settles it as <domain> <verb>: what went wrong, which sysctl and netplan
already use and which sorts and greps usefully.

Names the two habits already in the tree and why each is wrong. An error
that announces it failed says nothing the wrapping did not, because these
are almost always wrapped. An error naming the command rather than the
operation leaks the implementation into a message a user reads, and stops
being true when the implementation changes.

The four sentinels stay exactly as they are. They are compared with
errors.Is rather than read, so the convention does not apply to them.

Refs osapi-io/osapi#565

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FuKUsHFG1EqZXamffh9M2c
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

Thank you for contributing to this project! 😊🕹️

@retr0h
retr0h merged commit 59a42f1 into main Oct 2, 2026
5 checks passed
@retr0h
retr0h deleted the docs/provider-error-shape branch October 2, 2026 04:31
retr0h added a commit to osapi-io/osapi that referenced this pull request Oct 2, 2026
Applies osapi-io/specs#245 to the file provider, which carried the worst of
the four shapes. Every error here began "failed to", which is the one Go's
own guidance argues against: these are almost always wrapped, so the reader
saw "deploy schedule entry: failed to execute template: ..." where the words
carried nothing the context had not.

They now read "file deploy: ...", "file template: ...", "file status: ..."
and "file undeploy: ...", named for the operation rather than for the fact
that something went wrong.

Two things the tests caught. The ownership error spans two lines, so a
regex anchored on fmt.Errorf missed it. And the template failures are
wrapped by deploy rather than raised by template, so the assertion naming
the originating operation was wrong until it was read off the actual error.

Five providers still carry the older shapes. This is the first.

Refs #565


Claude-Session: https://claude.ai/code/session_01FuKUsHFG1EqZXamffh9M2c

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
retr0h added a commit to osapi-io/osapi that referenced this pull request Oct 2, 2026
Three more providers onto <domain> <verb>, following osapi-io/specs#245.

schedule used <verb> <domain> and named cron, which the rename had already
taken out of every other layer: "create cron entry" becomes "schedule
create", "invalid cron entry name" becomes "schedule: name must not be
empty".

ntp used a bare <domain>, so five different operations all reported "ntp:"
and the reader could not tell a failed write from a failed chronyc call.
Each now names its operation.

certificate used <verb> <domain>, the mirror of the convention.

Each has an exact-match and a %q variant of its name validation, and the %q
one is easy to miss because the exact-match string reads like the whole rule.
certificate's was, until the tests caught it.

Two providers left: service uses a bare <domain> and user has
"chpasswd failed", which names a command rather than an operation.

Refs #565


Claude-Session: https://claude.ai/code/session_01FuKUsHFG1EqZXamffh9M2c

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant