Add the labelsync sentinel errors, KindOf, and the XDG paths - #49
Merged
Merged
Conversation
All 18 sentinels from the design's error table land at once, even though
most have no call site yet: downstream packages import them constantly and
drip-feeding them would churn every in-flight branch.
KindOf maps each to its stable kind string, which is a public contract —
it is rendered as error_kind in JSON output. Tests cover the round-trip
for every sentinel, recognition through three layers of fmt.Errorf("%w"),
uniqueness of the kind strings, and "" for nil and unrelated errors.
ConfigDir and CacheDir resolve $XDG_CONFIG_HOME/labelsync and $XDG_CACHE_HOME/labelsync via github.com/adrg/xdg. The labels.yml and labels.yaml constants, plus the ordered ConfigFileNames, give the config loader the two spellings it must search and the pair it must reject as ambiguous.
The error table already listed the sentinels, but not the rule that makes them work: wrap with %w, never render with %v or rebuild the error, or both errors.Is and KindOf stop matching.
CI runs go test -race; the local task did not, so a data race could pass locally and fail on the PR.
Ilyes512
force-pushed
the
feat/GH-12-labelsync-sentinels-xdg
branch
from
August 7, 2026 15:27
58b9f34 to
4b390d6
Compare
TestKindOf_CoversEverySentinel checked len(allSentinels) against a hardcoded 18, which is exactly as easy to forget to bump as the table itself is to update — and a removal paired with an addition kept the count right while the table went wrong. declaredSentinels parses the package's own non-test files with go/parser and collects every exported package-level Err* variable, so the expected set now comes from errors.go. The check runs both ways: declared but untabled, and tabled but no longer declared. Reflection cannot enumerate package-level vars, so the table gained a name column to compare against.
The error table said what the sentinels are, not what adding one costs. Three places have to move together — the table, KindOf, and allSentinels — and the test now parses the package source to enforce the third, so say so next to the table where someone adding a sentinel will read it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
internal/labelsync/errors.go: all 18 sentinels from the design's error table, plusKindOfmapping each to its stable kind string — theerror_kindcontract for JSON output.internal/labelsync/configuration.go:ConfigDirandCacheDirovergithub.com/adrg/xdg, plus thelabels.yml/labels.yamlconstants and the orderedConfigFileNames.fmt.Errorf("%w"), and kinds are unique.docs/design.mdnow states the%wwrapping rule and what adding a sentinel costs, andtask testruns with-raceto match CI.Keeping the sentinel table honest
The coverage test originally compared
len(allSentinels)against a hardcoded18, which is exactly as easy to forget to bump as the table itself is to update — and a removal paired with an addition would keep the count right while the table went wrong.declaredSentinelsnow parses the package's own non-test files withgo/parserand collects every exported package-levelErr*variable, so the expected set comes fromerrors.gorather than a constant. The check runs both ways:allSentinelsallSentinelsbut no longer declaredReflection cannot enumerate package-level vars, so the table gained a
namecolumn to compare against. At.Fatalguards the empty-glob case, so a broken working directory cannot quietly turn the test into a no-op.Verified by temporarily adding a sentinel to
errors.goand watching the test fail with the expected message.Closes #12