Repository navigation
fix: carry error codes on the error, not in the message text - #183
Merged
Merged
Conversation
theCodeDrift
force-pushed
the
fix/typed-error-codes
branch
from
August 26, 2026 04:41
bfe033f to
1769de6
Compare
`rule create` and `rule improve` picked between `AUTH_REQUIRED` and `NO_GITHUB_REMOTE` by running `/git remote|origin/i` over the human-readable message `resolveIdentity` threw. The stable codes exist so a machine consumer never parses English, and the code itself was chosen by parsing English. It was correct only because both repository-URL messages happen to contain the words "git remote". Each expected failure inside `resolveIdentity` now throws a `CLIError` carrying its own `CLIErrorCode`, following the class the CLI already uses for this. Both call sites read that field through one shared `identityFailureCode` helper, so they cannot drift. The org-resolution step gets no code because it cannot fail: `fetchWhoami` swallows every network and HTTP error, and `decodeOrgId` falls back to `NIL_ORG_ID`. An unexpected throw there reports `INTERNAL_ERROR`. Tests assert the `--json` envelope's `code` for every failure origin, on both commands, so a reworded message fails a test instead of silently changing what a consumer is told. Fixes #181 Refs #179
theCodeDrift
force-pushed
the
fix/typed-error-codes
branch
from
August 26, 2026 05:02
1769de6 to
534ee6a
Compare
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.
The bug
rule createandrule improvechose their machine-readable error code by running a regex over a human-readable error message:The stable codes exist so a machine consumer never has to parse English. Here the code itself was picked by parsing English, in two independently driftable copies. It was correct only because both repository-URL failure messages happen to contain the words "git remote". Reword either one, translate it, or add a fourth failure mode, and every
--jsonconsumer is silently told to log in when the real problem is that the project has no repository.This is the same shape as the styleguide's rule about not reconstructing a fact by parsing the producer's output. The producer already knew the code.
The fix
Option 1 from the issue, and it needed no new machinery:
CLIErroralready exists inpackages/cli/src/util/cli-error.tsand already carries an optionalCLIErrorCode, which the telemetry runner already reads. Every expected failure insideresolveIdentitynow throws aCLIErrorwith its code attached:packages/cli/src/auth/identity.tsthrowsAUTH_REQUIREDwhen there is no token.packages/cli/src/util/git-remote.tsthrowsNO_GITHUB_REMOTEfrom both of its failure sites.Both call sites in
packages/cli/src/commands/rules.tsread the field through one sharedidentityFailureCode(error)helper, so they cannot drift and neither can invent a code of its own.What
resolveOrgSubjectactually throwsNothing. This was the fourth failure origin in the issue, and it turns out not to be a failure origin at all:
fetchWhoamiwraps the whole request intry/catch, returnsundefinedon any thrown error, and returnsundefinedon an HTTP error response too.resolveCurrentOrgreturnsundefinedwhen no org matches the repo's remotes.resolveOrgSubjectthen falls back todecodeOrgId(token) ?? NIL_ORG_ID, so a subject is always produced.So there is no failure to give a code to, and no new
CLIErrorCodewas added. Anything that does escape that step is a CLI bug rather than a state the caller can act on, andidentityFailureCodereports it asINTERNAL_ERROR. A test inerror-envelope.test.tspoints the CLI at an unroutable API base url and asserts thatresolveOrgSubjectresolves rather than rejecting, so if it ever grows a throw, that test fails and the throw gets a code of its own deliberately.Origins 2 and 3 stay collapsed on
NO_GITHUB_REMOTE"Not a git repository / no
origin" and "originis not on github.com" remain one code, for two reasons:CLIErrorCodeis a public interface (the recipes name these codes in their## Errorssections), so adding one needs a consumer that would act differently, and there isn't one yet. Both conditions mean the same thing to a caller: no verifiable GitHub org identity for this project, so the remote tier cannot run. The remedy differs (git initplus a GitHub remote, versus the tier genuinely not applying to a GitLab or Gitea project), but nothing in the CLI branches on that today.throwsite, so A GitHub remote should not be a precondition for Taskless #179's capability-boundary work can separate them, or introduce a distinct code for the non-GitHub case, by editing the throw rather than by teaching a matcher a new phrase. That is the property this PR is actually buying.Both are asserted separately in tests, so the day they should diverge, the tests are already there to record it.
Behavior change for
--jsonconsumersThe emitted code is unchanged for every reachable scenario:
AUTH_REQUIREDwhen not logged in,NO_GITHUB_REMOTEfor both repository-URL failures. One code changes in a scenario that is unreachable today: an unexpected throw from the org-resolution step now reportsINTERNAL_ERRORinstead of whichever of the two codes its wording happened to match. The changeset says this plainly.Tests
packages/cli/test/error-envelope.test.tsgains aresolveIdentity failure codes (--json)block asserting the envelope'scodefor each origin, on bothrule createandrule improve:TASKLESS_TOKENstrippedAUTH_REQUIREDoriginNO_GITHUB_REMOTEorigingit initplus a gitlab.com / gitea remoteNO_GITHUB_REMOTEOne further test pins the property that replaced the regex directly: the
AUTH_REQUIREDfailure keeps its code while its message contains neither "git remote" nor "origin". Under the old implementation that message was the only thing standing between a consumer and the wrong code.runClinow strips an ambientTASKLESS_TOKENfrom the child environment, so a logged-in developer and CI exercise the same first step ofresolveIdentity.pnpm typecheck,pnpm lint, and the full suite (837 tests, 53 files) all pass.Fixes #181
Refs #179