@sibylline-labs/eris 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +146 -2
  4. package/bin/eris.ts +42 -0
  5. package/docs/ARCHITECTURE.md +188 -0
  6. package/docs/COMPLETION.md +106 -0
  7. package/docs/ERRORS.md +632 -0
  8. package/docs/OUTPUT-CONTRACT.md +219 -0
  9. package/docs/README.md +30 -0
  10. package/docs/UPDATE.md +159 -0
  11. package/docs/YAK.md +395 -0
  12. package/docs/dev/ADDING-A-COMMAND.md +79 -0
  13. package/docs/dev/DEVELOPMENT.md +88 -0
  14. package/docs/dev/LAYOUT.md +74 -0
  15. package/docs/dev/PLUGINS.md +382 -0
  16. package/docs/dev/RELEASING.md +157 -0
  17. package/docs/dev/TESTING.md +84 -0
  18. package/docs/dev/surfaces/secrets.md +95 -0
  19. package/package.json +55 -4
  20. package/packages/yak-contract/package.json +28 -0
  21. package/packages/yak-contract/schema/yak-v2.schema.json +416 -0
  22. package/packages/yak-contract/src/index.ts +1 -0
  23. package/packages/yak-contract/src/v2.ts +375 -0
  24. package/plugins/index.ts +12 -0
  25. package/plugins/yak/handlers.ts +128 -0
  26. package/plugins/yak/index.ts +51 -0
  27. package/plugins/yak/login.ts +300 -0
  28. package/plugins/yak/yak-context.ts +280 -0
  29. package/plugins/yak/yak-identity.ts +399 -0
  30. package/plugins/yak/yak-redact.ts +686 -0
  31. package/plugins/yak/yak-setup.ts +133 -0
  32. package/plugins/yak/yak-submit.ts +1166 -0
  33. package/plugins/yak/yak.ts +485 -0
  34. package/src/build.ts +248 -0
  35. package/src/cli/args.ts +434 -0
  36. package/src/cli/commands/completion/bash.ts +24 -0
  37. package/src/cli/commands/completion/domain.ts +203 -0
  38. package/src/cli/commands/completion/emit.ts +246 -0
  39. package/src/cli/commands/completion/shell.ts +24 -0
  40. package/src/cli/commands/completion/zsh.ts +24 -0
  41. package/src/cli/commands/completion.ts +48 -0
  42. package/src/cli/commands/update/github.ts +328 -0
  43. package/src/cli/commands/update/opt-in.ts +32 -0
  44. package/src/cli/commands/update/refusal.ts +21 -0
  45. package/src/cli/commands/update/release.ts +351 -0
  46. package/src/cli/commands/update/replace.ts +143 -0
  47. package/src/cli/commands/update/semver.ts +48 -0
  48. package/src/cli/commands/update.ts +47 -0
  49. package/src/cli/commands/usage.ts +68 -0
  50. package/src/cli/commands/version.ts +29 -0
  51. package/src/cli/compose.ts +273 -0
  52. package/src/cli/output.ts +81 -0
  53. package/src/cli/plugin.ts +85 -0
  54. package/src/cli/registry.ts +180 -0
  55. package/src/cli/router.ts +31 -0
  56. package/src/cli/ts-verb.ts +126 -0
  57. package/src/main.ts +223 -0
  58. package/src/runtime/env.ts +16 -0
  59. package/src/runtime/eris-assets.d.ts +5 -0
  60. package/src/runtime/home.ts +306 -0
  61. package/src/runtime/logging.ts +43 -0
  62. package/src/runtime/process.ts +388 -0
  63. package/src/runtime/settings.ts +191 -0
  64. package/src/sdk.ts +62 -0
  65. package/testing/build-child.ts +55 -0
  66. package/testing/fake-runner.ts +23 -0
  67. package/testing/index.ts +16 -0
  68. package/ts/lib/eris-fail.ts +180 -0
  69. package/ts/lib/pool.ts +63 -0
package/docs/ERRORS.md ADDED
@@ -0,0 +1,632 @@
1
+ # eris failure model
2
+
3
+ Hard failures go through `renderFailureBlock` / `emitFailureAndExit`
4
+ (`ts/lib/eris-fail.ts`), which print one
5
+ actionable block and exit `1` immediately — a first failure never cascades
6
+ into follow-on ERROR lines. Output is one-line-per-field with stable prefixes
7
+ (all on stderr; stdout stays clean):
8
+
9
+ ```
10
+ ERROR <what failed> # concrete — names the exact key/env/secret slot
11
+ slug: <slug> # stable machine key; heading in this file
12
+ fix: <line> # the exact command to run / eris.yaml block to add
13
+ docs: <local path> # $ERIS_HOME/docs/... (the checkout, or a binary's asset home)
14
+ docs: <github url> # same doc on github.com/sibylline-labs/eris
15
+ ```
16
+
17
+ This block is a **parseable format, not a layout** — consumers line-prefix-parse
18
+ it today. Its full grammar (field order, the exact prefixes *including the
19
+ single trailing space* after `slug:` / `fix:` / `docs:`, the one-block rule, and
20
+ what a consumer may and may not assume) is specified in
21
+ [OUTPUT-CONTRACT.md §4](OUTPUT-CONTRACT.md#4-the-failure-block). Change the
22
+ grammar there and here in the same commit, never in one alone.
23
+
24
+ Agents: match on the `slug:` line, apply the `fix:` lines verbatim (yaml blocks
25
+ are indented continuation lines), read `docs:` for depth. Error messages name
26
+ the slot/path being resolved — never secret contents.
27
+
28
+ Exit codes: `1` = hard failure (a failure block or a `die`). `2` = idempotent
29
+ no-op where a command documents one.
30
+ The full stream + exit-code contract is
31
+ [OUTPUT-CONTRACT.md §1](OUTPUT-CONTRACT.md#1-the-stream-split).
32
+
33
+ ## Slugs
34
+
35
+ ### flag-value-missing
36
+
37
+ A value-taking flag was given no value, or was given the NEXT FLAG as its
38
+ value. A bare "is it non-empty" guard accepts both: `--owner --yes` would store
39
+ `owner: "--yes"` at exit 0 and swallow the `--yes`, which then re-arms the
40
+ confirmation gate the caller had already answered, so the parser
41
+ (`src/cli/args.ts`) refuses a value flag followed by another flag. The joined form (`--flag=value`) is checked for emptiness
42
+ only: an empty `--required=` once registered the opposite of what was asked
43
+ for.
44
+
45
+ ### output-format-unknown
46
+
47
+ `--output` was given something other than `text` or `json` (or was given no
48
+ value at all). The flag is global and is parsed once in the entrypoint
49
+ (`parseGlobal`, `src/cli/args.ts`); there is no `ERIS_OUTPUT` env var, so
50
+ the flag is the only selector. Fix: `eris --output json <command>` for the
51
+ machine rendering, `--output text` (the default) for the human one. See
52
+ [OUTPUT-CONTRACT.md](OUTPUT-CONTRACT.md#2-the-flag).
53
+
54
+ ### output-json-unsupported
55
+
56
+ `--output json` was asked of a command that has no JSON rendering. eris refuses
57
+ rather than falling back to the human rendering: a silent fallback hands a
58
+ parser a table, and the caller discovers it as a JSON syntax error somewhere
59
+ downstream instead of as a refusal here.
60
+
61
+ The refusal's `fix:` lines name the surfaces that accept it, including every
62
+ json row a plugin adds. Full table: [OUTPUT-CONTRACT.md](OUTPUT-CONTRACT.md#3-machine-surfaces).
63
+
64
+ ### command-locked
65
+
66
+ A plugin command belongs to a product this workspace has not bought, so it is
67
+ locked (`src/cli/compose.ts`). Plugins compile into the binary, and the
68
+ entitlement check runs per command line: the command's help still prints, with a
69
+ `Locked:` line, and `eris --help` marks the group `[locked: <product>]`. Nothing
70
+ from the plugin's handler runs.
71
+
72
+ Fix: add the named product to the workspace, then run the command again.
73
+ Authoring side: [dev/PLUGINS.md](dev/PLUGINS.md#entitlements).
74
+
75
+ ### eris-bun-missing
76
+
77
+ > Raised by the `eris` shim, before any command runs.
78
+
79
+ `bun` is not on `PATH`. The `eris` entrypoint is a POSIX `sh` shim that execs
80
+ `src/main.ts` under bun, and that router runs every command, so no command runs
81
+ without bun.
82
+ The shim renders this block itself, since nothing past it can run.
83
+
84
+ Fix: install bun (<https://bun.sh>), then run `bun install --frozen-lockfile` in the
85
+ eris checkout (the `node_modules` the verbs need). An installed package
86
+ (`npx @sibylline-labs/eris`) needs bun too.
87
+
88
+ ### eris-cache-unwritable
89
+
90
+ > Raised by the entrypoint (`src/main.ts`) in a compiled eris binary, before the
91
+ > command runs.
92
+
93
+ The compiled binary carries the repo files it reads (the docs a failure block
94
+ names) and writes one copy per build to
95
+ `${ERIS_CACHE_DIR:-${XDG_CACHE_HOME:-~/.cache}/eris}/<build id>/` the first time
96
+ it runs. That directory could not be written: the filesystem is read-only or
97
+ full, the directory belongs to someone else, or a file sits where a directory
98
+ should be. The `ERROR` line names the cache directory and the operating
99
+ system's own reason.
100
+
101
+ `eris --help`, a `--help` on any command, and `eris completion` still run, since
102
+ they read none of those files and are how an operator finds this out. Every other
103
+ command is refused, because it may read one.
104
+
105
+ No asset home exists to hold the docs, so this block's first `docs:` line is the
106
+ repo-relative path rather than an absolute one. The second is the GitHub URL.
107
+
108
+ Fix: set `ERIS_CACHE_DIR` to a directory eris can write, or `XDG_CACHE_HOME` (eris
109
+ uses `$XDG_CACHE_HOME/eris`). On a read-only root filesystem, point it at a writable
110
+ volume. A checkout or an installed package writes no cache.
111
+
112
+ ### eris-deps-not-installed
113
+
114
+ > Raised by the TypeScript verb runner (`src/cli/ts-verb.ts`), when a verb's
115
+ > module graph names a package the checkout's `node_modules` cannot resolve.
116
+
117
+ A TypeScript verb (every row the registry routes to `ts`, which is every row) is
118
+ imported lazily, and one of its packages is
119
+ missing: `node_modules` is absent, or is the install from before a pull that
120
+ added a dependency. The `ERROR` line names the package and the source file that
121
+ asked for it, instead of a bare `ERROR ResolveMessage: Cannot find package '…'`.
122
+
123
+ Fix: `bun install --frozen-lockfile` from the eris checkout (the `fix:` line names
124
+ the directory). eris does not run it for you: an inline install is unbounded and reaches the network mid-command.
125
+
126
+ Only a missing PACKAGE is reported this way. A missing relative module is a defect
127
+ in the checkout's own source and still crashes with bun's message.
128
+
129
+ ### eris-verbose-fetch-set
130
+
131
+ > Raised by the package's `eris` command (`bin/eris.ts`), before any command runs.
132
+
133
+ `BUN_CONFIG_VERBOSE_FETCH` is set in the environment of an `eris` started from an
134
+ installed package (`npm i -g @sibylline-labs/eris`, `bun add -g`, `npx`, `bunx`).
135
+ Under that variable bun prints every fetch to stderr, the `Authorization` bearer
136
+ and the request body included, and for a secret write the body is the value. bun
137
+ reads it from its own environment when it fetches, and `delete process.env.X`
138
+ does not reach that read. The checkout's `eris` shim removes the variable before
139
+ bun starts; an installed command is started by its shebang and has no shim, so it
140
+ refuses to run rather than depend on an override from inside the process or print
141
+ a bearer token. No command runs, `--help` and `--version` included.
142
+
143
+ Fix: unset the variable, for the shell (`unset BUN_CONFIG_VERBOSE_FETCH`) or for
144
+ one command (`env -u BUN_CONFIG_VERBOSE_FETCH eris <command>`). The checkout's
145
+ shim and a compiled binary remove it themselves and never raise this.
146
+
147
+ ### update-not-compiled
148
+
149
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) before any request.
150
+
151
+ This eris runs from source (a checkout, `./eris`), not from a compiled release
152
+ binary. A checkout has no single file to replace: it is a working tree under
153
+ version control.
154
+
155
+ Fix: update the checkout, `git pull && bun install --frozen-lockfile` in the
156
+ directory the `fix:` line names.
157
+
158
+ ### update-unsupported-platform
159
+
160
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) before any request.
161
+
162
+ Releases publish a binary for `darwin-arm64` and `linux-x64` only, and this eris
163
+ was compiled for another platform (built from source on a machine with another
164
+ OS or CPU). There is no asset to download for it.
165
+
166
+ Fix: build eris from source for this platform and run that build; see
167
+ "From source" in the README. Updating means pulling and building again.
168
+
169
+ ### update-api-invalid
170
+
171
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) before any request.
172
+
173
+ `ERIS_UPDATE_API`, the override for the releases API base (a GitHub Enterprise
174
+ host, or a test server), is set to something eris will not use: it is not a URL,
175
+ its scheme is neither `https` nor `http`, it is `http` to a host that is not
176
+ loopback, or it carries credentials, a query or a fragment. The refusal exists so
177
+ a stray variable cannot send a token in clear text to a host nobody chose. The
178
+ `ERROR` line shows the scheme and host only.
179
+
180
+ Fix: set it to an `https://` URL (for example `https://ghe.example.com/api/v3`),
181
+ or unset it to use `https://api.github.com`.
182
+
183
+ ### update-token-invalid
184
+
185
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) before any request.
186
+
187
+ `GH_TOKEN` (or, when it is unset or blank, `GITHUB_TOKEN`) holds something no
188
+ GitHub token contains: a space or line break inside the value, or a non-ASCII
189
+ character. Leading and trailing whitespace is ignored, so a token read from a file
190
+ with its newline still works. The refusal names the variable and never shows its
191
+ value: sending such a value makes the HTTP client report an error that quotes it.
192
+
193
+ Fix: set the variable to the token alone, or unset it.
194
+
195
+ ### update-unreachable
196
+
197
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) when the releases API or the download host does not
198
+ > answer.
199
+
200
+ The connection was refused, the name did not resolve, the connection dropped, or
201
+ a host went quiet: the API did not answer within 30 seconds, or a download sent
202
+ nothing for 60 seconds. Nothing was changed; a download in progress is removed.
203
+
204
+ Fix: check the network connection (eris update needs https access to
205
+ `api.github.com`, and to GitHub's release storage host for the download), then
206
+ run `eris update` again.
207
+
208
+ ### update-rate-limited
209
+
210
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) when GitHub refuses the request for its rate limit.
211
+
212
+ GitHub answered 429, or 403 with the rate limit spent. An anonymous client gets
213
+ 60 requests an hour per address; an authenticated one gets far more. `eris
214
+ update` makes up to three API requests per run. When GitHub reported when the
215
+ limit resets, the `ERROR` line says so.
216
+
217
+ Fix: set `GH_TOKEN` (or `GITHUB_TOKEN`) to a GitHub token. Any token works for
218
+ a public repository, with no scopes. If one is already set, its own limit is
219
+ spent: wait for the reset time, then run `eris update` again.
220
+
221
+ ### update-needs-token
222
+
223
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) when GitHub answers 404 and no token is set.
224
+
225
+ GitHub answers 404, not 403, to a request that is not allowed to see a private
226
+ repository, so an anonymous 404 does not say whether the release is missing or
227
+ the repository is private. This refusal is the anonymous case.
228
+
229
+ Fix: if the repository is private, set `GH_TOKEN` (or `GITHUB_TOKEN`) to a token
230
+ that can read it and run `eris update` again. Otherwise the release does not
231
+ exist: list them at `https://github.com/<owner>/<repo>/releases`.
232
+
233
+ ### update-release-not-found
234
+
235
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) when the release is not installable.
236
+
237
+ Either GitHub answered 404 to a request that carried a token, so the repository
238
+ has no such release (or the token cannot see the repository), or the release
239
+ found is a draft, or a prerelease that `--to` did not name. `eris update` installs
240
+ the latest published release; a prerelease only with `--to <tag>`.
241
+
242
+ Fix: check the tag against the repository's releases page. With a private
243
+ repository, the token must be able to read it.
244
+
245
+ ### update-api-error
246
+
247
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) for any other answer the API gives.
248
+
249
+ An answer that is not a release and not one of the cases above: a 500, a 403 that
250
+ is not the rate limit, a 401 (the token was rejected, or the API wants one), a
251
+ body that is not a release record, a redirect chain that is too long or leaves
252
+ for a scheme eris will not follow, or a download larger than eris will accept.
253
+ The `ERROR` line names the host and the status.
254
+
255
+ Fix: for a 401, unset `GH_TOKEN` or replace it with a valid token. For a 403 on a
256
+ private repository, the token must be able to read it. Otherwise retry in a few
257
+ minutes.
258
+
259
+ ### update-asset-missing
260
+
261
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) before it downloads the binary.
262
+
263
+ The release exists but cannot be installed: it lists no `eris-<os>-<arch>` for
264
+ this platform, no `SHA256SUMS`, or a `SHA256SUMS` with no line for this
265
+ platform's binary. eris never installs a binary it cannot check, so a release
266
+ without its checksum is refused. The same slug covers an asset the release lists
267
+ but the API answers 404 for.
268
+
269
+ Fix: choose another release with `eris update --to <tag>`.
270
+
271
+ ### update-checksum-mismatch
272
+
273
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) after the download, before anything is replaced.
274
+
275
+ The downloaded binary's sha256 differs from the one in the release's
276
+ `SHA256SUMS`. The `ERROR` line gives both. The partial file is removed and the
277
+ installed binary is untouched.
278
+
279
+ Fix: run `eris update` again. If it repeats, the release is corrupt or has been
280
+ tampered with: do not install it.
281
+
282
+ ### update-target-unwritable
283
+
284
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)), before any request when it can tell.
285
+
286
+ The directory that holds the eris binary cannot be written, or a file there could
287
+ not be created: a read-only filesystem, a directory owned by another user (a
288
+ system-wide install), a full disk. `eris update` stages the new binary beside the
289
+ old one, so it needs write access to that directory, not only to the binary.
290
+
291
+ Fix: run `eris update` as a user who owns the directory, or reinstall eris into a
292
+ directory you own.
293
+
294
+ ### update-smoke-failed
295
+
296
+ > Raised by a distribution's release `eris update` ([UPDATE.md](UPDATE.md#the-release-eris-update-distributions)) after the replacement.
297
+
298
+ The new binary was renamed into place and then run once (`eris --output json
299
+ --version`); it did not start, exited non-zero, did not answer within 20 seconds,
300
+ or reported a version other than the release's tag. The previous binary is put
301
+ back from `<binary>.old` and the `ERROR` line says so. If it could not be put
302
+ back, the `ERROR` line says that and the `fix:` line gives the `mv` that does it.
303
+
304
+ Fix: the release may be broken. Retry later, or install another with `eris update
305
+ --to <tag>`.
306
+
307
+ ### yak-unknown-verb
308
+
309
+ `eris yak <verb>` was called with a verb other than `issue`, `suggest`, or
310
+ `fatal`. Fix: use one of those three.
311
+
312
+ ### yak-unknown-flag
313
+
314
+ `eris yak` was given a flag it does not recognise. The full flag set is
315
+ `--summary`, `--body`/`--body-file`, `--evidence`/`--evidence-file`,
316
+ `--repro`, `--no-redact`, `--help`. Fix: `eris yak <verb> --help`.
317
+
318
+ ### yak-positional-arg
319
+
320
+ `eris yak <verb>` was given a bare word, usually the report text itself
321
+ (`eris yak issue "the hook blocked a legal edit"`). The text rides on flags,
322
+ not positionals. Fix: `eris yak <verb> --summary "<one line>" --body "<what
323
+ happened>"`, or `--body-file <path|->` for a longer body.
324
+
325
+ ### yak-flag-missing-value
326
+
327
+ A value-taking flag (`--summary`, `--body`, `--body-file`, `--evidence`,
328
+ `--evidence-file`, `--repro`) was the last token on the command line, with
329
+ nothing after it. Fix: pass the value, or drop the flag.
330
+
331
+ ### yak-summary-required
332
+
333
+ `--summary` is required and was missing or empty. Fix: pass `--summary
334
+ "<text>"`, ≤200 chars.
335
+
336
+ ### yak-body-required
337
+
338
+ Neither `--body` nor `--body-file` resolved to a non-empty string. Fix: pass
339
+ one of them.
340
+
341
+ ### yak-body-conflicting-source
342
+
343
+ Both `--body` and `--body-file` were given. They are mutually exclusive — pass
344
+ exactly one.
345
+
346
+ ### yak-evidence-conflicting-source
347
+
348
+ Both `--evidence` and `--evidence-file` were given. They are mutually
349
+ exclusive — pass exactly one, or neither (evidence is optional).
350
+
351
+ ### yak-body-file-unreadable
352
+
353
+ `--body-file <path>` (or `--evidence-file <path>`, which raises the same slug
354
+ under the `evidence` label) could not be read — the path does not exist, or
355
+ the process lacks permission. Fix: check the path, or read stdin with `-`.
356
+
357
+ ### yak-evidence-file-unreadable
358
+
359
+ The `--evidence-file` counterpart of
360
+ [`yak-body-file-unreadable`](#yak-body-file-unreadable) — same cause, same
361
+ fix, raised for the evidence field.
362
+
363
+ ### yak-stdin-is-a-tty
364
+
365
+ `--body-file -` (or `--evidence-file -`) reads stdin, but stdin is a terminal
366
+ with nothing piped into it. Refused immediately rather than hanging forever on
367
+ a read that would never return. Fix: pipe the body in, e.g. `cat report.txt |
368
+ eris yak issue --summary "…" --body-file -`.
369
+
370
+ ### yak-stdin-conflict
371
+
372
+ `--body-file -` and `--evidence-file -` were both given — only one stdin
373
+ exists to satisfy either. Fix: pass the other as inline text (`--evidence
374
+ "…"`) or a real file path.
375
+
376
+ ### yak-summary-too-long
377
+
378
+ The (redacted) `summary` exceeds 200 chars. This is caught locally, before the
379
+ request, so the endpoint can never silently truncate it out of sync with the
380
+ idempotency key already derived from it. Fix: shorten it — redaction can grow
381
+ text, so check the real length with `--no-redact` if the count is surprising.
382
+
383
+ ### yak-body-too-long
384
+
385
+ The `--body-file`/`--evidence-file` counterpart of
386
+ [`yak-summary-too-long`](#yak-summary-too-long) for the `body` field
387
+ (≤4000 chars). Same cause, same fix.
388
+
389
+ ### yak-evidence-too-long
390
+
391
+ The `evidence` field exceeds 20000 chars. Same cause and fix as
392
+ [`yak-summary-too-long`](#yak-summary-too-long).
393
+
394
+ ### yak-repro-too-long
395
+
396
+ The (redacted) `--repro <command>` exceeds 2000 chars — a local
397
+ courtesy bound, not a wire-contract one: `repro` rides inside `provenance`
398
+ (`provenance.repro.command`), which the contract package never
399
+ length-validates. Caught locally anyway, same reasoning as
400
+ [`yak-summary-too-long`](#yak-summary-too-long) — a repro is a command, not a
401
+ narrative, so 2000 chars is generous headroom over any real invocation. Fix:
402
+ shorten it — redaction can grow text, so check the real length with
403
+ `--no-redact` if the count is surprising.
404
+
405
+ ### yak-repo-too-long
406
+
407
+ A (redacted) `--repo <owner/name>` hint exceeds 200 chars — a target-repo hint rides
408
+ inside `provenance` (`provenance.repo.target_repos`), which the contract
409
+ package types and bounds at the same 200 chars. Caught here first, so the
410
+ error names `--repo` rather than a generic payload failure, same reasoning as
411
+ [`yak-repro-too-long`](#yak-repro-too-long) — a hint is a short `owner/name`
412
+ identifier, so 200 chars is generous headroom over any real one. Fix:
413
+ shorten it — redaction can grow text, so check the real length with
414
+ `--no-redact` if the count is surprising.
415
+
416
+ ### yak-repo-hints-too-many
417
+
418
+ More than 20 `--repo` hints were given on one submission. A
419
+ courtesy bound for the same DO-storage-hygiene reason as
420
+ [`yak-repo-too-long`](#yak-repo-too-long) — no single report genuinely spans
421
+ that many repos. Fix: narrow `--repo` to the repos this report actually
422
+ concerns.
423
+
424
+ ### yak-payload-invalid
425
+
426
+ Local validation against the Yaks contract package
427
+ (`@sibylline-labs/yak-contract`) failed for a reason other than a length bound
428
+ (e.g. an empty required field after redaction). The `ERROR` line names the
429
+ field and the schema's message.
430
+
431
+ ### yak-identity-id-too-long
432
+
433
+ `device_id` or `execution_id` exceeded the contract's 200-char bound. Unlike
434
+ [`yak-summary-too-long`](#yak-summary-too-long)/`yak-body-too-long`, this is
435
+ **never the caller's fault** — neither field is something an `eris yak`
436
+ invocation passes. `device_id` is resolved from `~/.eris/device-id` (a
437
+ corrupt or hand-edited file); `execution_id` from
438
+ `CLAUDE_CODE_SESSION_ID`/`CLAUDE_CODE_HOST_SESSION_ID` (an unusually long
439
+ value from the harness) or the shell ppid. Warn + exit 0 — the `WARN` line
440
+ names the real field and its real length; fix the identity source it names,
441
+ not the report.
442
+
443
+ ### yak-redaction-overflow
444
+
445
+ The caller's ORIGINAL `summary`/`body`/`evidence`/`repro` was within its
446
+ length bound, but the secret-scrub pass (a marker like `«redacted:aws-key»`
447
+ can be longer than the text it replaces) grew it past the limit. Warn + exit
448
+ 0, not misuse — the reported text is fine as authored. Retry with
449
+ `--no-redact` if the flagged text contains no real secret, or shorten it so
450
+ redaction has room to grow.
451
+
452
+ ### yak-deps-not-installed
453
+
454
+ `eris`'s own `node_modules` is missing, or present but missing a package
455
+ `eris yak`'s TypeScript core imports (an interrupted `bun install`, a stale
456
+ checkout). `eris yak` refuses rather than running an unbounded `bun install`
457
+ inline mid-task, or letting `bun` crash with a raw, unslugged `Cannot find
458
+ package '…'` (`plugins/yak/handlers.ts`). Fix: `cd` to the eris checkout and run
459
+ `bun install` yourself. It warns and exits 0, like
460
+ [`yak-token-unresolved`](#yak-token-unresolved): the command that reports a
461
+ broken harness must not become one more thing that stops the agent. Under
462
+ `--output json` it is the bare record on stdout (`delivered: false`, `error.slug`)
463
+ with stderr empty.
464
+
465
+ ### yak-url-unresolved
466
+
467
+ No longer raised. A Yaks base URL always resolves now: `ERIS_YAKS_URL`, then the
468
+ `"url"` in `~/.eris/yaks.json`, then the default `https://api.sibylline.group`.
469
+ An older eris warned with this slug (exit 0) when neither was set; with this
470
+ one, set `ERIS_YAKS_URL` only to use another endpoint.
471
+
472
+ ### yak-unreachable
473
+
474
+ The Yaks endpoint could not be reached at all (connection refused, DNS failure,
475
+ etc.) within the request's bound. Warn + exit 0 — the report was dropped, not
476
+ delivered; the agent's task is unaffected.
477
+
478
+ ### yak-timeout
479
+
480
+ The Yaks endpoint did not respond within the bounded request window (30s
481
+ default). No retry — matches the registry transport's own posture. Warn + exit 0.
482
+
483
+ ### yak-unauthorized
484
+
485
+ The Yaks endpoint answered `401`: it rejected the bearer token. Check the
486
+ `"token"` in `~/.eris/yaks.json` or `ERIS_YAKS_TOKEN`, and that the token is
487
+ registered with the endpoint. Retrying the same call changes nothing. Warn +
488
+ exit 0; the report was dropped, not delivered, and the agent's task is
489
+ unaffected. A problem with eris itself is reported on the vendor route, the eris
490
+ project's own tracker, rather than into the workspace.
491
+
492
+ ### yak-not-entitled
493
+
494
+ The Yaks endpoint answered `402`: the workspace is not entitled to Yaks (its
495
+ plan or seat does not include it). Ask whoever administers the workspace;
496
+ nothing on this machine fixes it. Warn + exit 0; the report was dropped, not
497
+ delivered, and the agent's task is unaffected. A problem with eris itself is
498
+ reported on the vendor route, the eris project's own tracker, rather than into the
499
+ workspace.
500
+
501
+ ### yak-payload-too-large
502
+
503
+ The Yaks endpoint answered `413`: the submission exceeded the endpoint's
504
+ whole-body size cap. The per-field length checks pass locally first, so the sum
505
+ is what overflowed; trim `--evidence` (the largest field), `--repro` or the
506
+ `--repo` hints. Warn + exit 0; the
507
+ report was dropped, not delivered, and the agent's task is unaffected. A problem
508
+ with eris itself is reported on the vendor route, the eris project's own tracker,
509
+ rather than into the workspace.
510
+
511
+ ### yak-transport-error
512
+
513
+ The Yaks endpoint responded, but not usably: a non-2xx status other than the
514
+ `401`, `402` and `413` that have their own slugs
515
+ ([`yak-unauthorized`](#yak-unauthorized), [`yak-not-entitled`](#yak-not-entitled),
516
+ [`yak-payload-too-large`](#yak-payload-too-large)), an unparseable 2xx body, or a
517
+ 2xx that does not match the receipt contract (`YakReceipt` in
518
+ `@sibylline-labs/yak-contract`). Warn + exit 0; the `WARN` line names the HTTP
519
+ status or the parse failure.
520
+
521
+ ### yak-config-permissive
522
+
523
+ `~/.eris/yaks.json` is readable by users other than its owner (not mode
524
+ `0600`). Refused, never read — a credential file with group/world read is a
525
+ leak surface. Fix: `chmod 0600 ~/.eris/yaks.json`.
526
+
527
+ ### yak-config-malformed
528
+
529
+ `~/.eris/yaks.json` exists but is not valid JSON, or is not a JSON object.
530
+ Fix: recreate it as `{"url": "...", "token": "..."}`.
531
+
532
+ ### yak-config-unwritable
533
+
534
+ `eris login` or `eris logout` could not write `~/.eris/yaks.json` (the `ERROR`
535
+ line names the path and the errno). The previous file is untouched: the new
536
+ one is written to a temp file beside it and renamed into place. Fix: make
537
+ `~/.eris` writable by you, then run the command again.
538
+
539
+ ### yak-token-unresolved
540
+
541
+ No bearer token resolved for the Yaks endpoint — neither `ERIS_YAKS_TOKEN` nor a
542
+ `"token"` field in a readable `~/.eris/yaks.json`. Environment/config, warn +
543
+ exit 0. Fix: run `eris login` and approve the code it shows in your browser; it
544
+ writes the token to `~/.eris/yaks.json`. Approving is a human step, not
545
+ something an agent can do mid-task.
546
+
547
+ ### yak-device-id-unwritable
548
+
549
+ `~/.eris/device-id` does not exist and could not be minted (directory not
550
+ writable, disk full, etc.). Warn + exit 0 — the report still attempts to send
551
+ with whatever identity resolved.
552
+
553
+ ### yak-key-mismatch
554
+
555
+ Informational only — not a failure, not a warn-and-drop. The submission WAS
556
+ delivered (`delivered: true`), but the idempotency key this CLI derived
557
+ locally does not match the one the endpoint's receipt reports back, which is the
558
+ only canonicalization-drift signal the contract exposes (the schema and the
559
+ endpoint must derive identically from the same redacted payload). Printed as a
560
+ `WARN` line alongside a successful receipt in text mode; worth reporting
561
+ upstream if seen. Under `--output json` this WARN is suppressed like every
562
+ other diagnostic — the record's `key_mismatch` field carries the
563
+ same fact instead.
564
+
565
+ ### yak-no-redact
566
+
567
+ Informational only — printed whenever `--no-redact` is passed, regardless of
568
+ the outcome, so the operator knows the secret-scrub pass did not run on this
569
+ submission. Not a failure. Under `--output json` this WARN is suppressed like
570
+ every other diagnostic — `redactions.skipped: true` on the record
571
+ carries the same fact instead, which matters because a skipped scrub and a
572
+ scrub that ran and found nothing otherwise look identical (`count: 0,
573
+ classes: []`).
574
+
575
+ ### login-url-unresolved
576
+
577
+ No longer raised. `eris login` always has an endpoint: `--url`, then
578
+ `ERIS_YAKS_URL`, then the `"url"` in `~/.eris/yaks.json`, then the default
579
+ `https://api.sibylline.group`. An empty `--url` is refused as
580
+ [`flag-value-missing`](#flag-value-missing). An older eris raised this slug when
581
+ none of the first three was set; with this one, run `eris login`.
582
+
583
+ ### login-unreachable
584
+
585
+ The endpoint could not be reached when the device login started, or stopped
586
+ answering (a dropped connection or a 5xx, more than three polls in a row) while
587
+ waiting for approval. Nothing was written. Fix: check the url and your
588
+ connection, then run `eris login` again.
589
+
590
+ ### login-refused
591
+
592
+ The endpoint answered, but not with a device login: it refused to start one
593
+ (the `ERROR` line carries its error code, from either the RFC `{"error": …}`
594
+ or the `{"error": {"code", "message"}}` shape, e.g. `invalid_device`,
595
+ `device_unconfigured`), sent a malformed response, or refused the device code
596
+ with a code other than the four RFC 8628 ones. Nothing was written. Fix: check
597
+ the url points at an endpoint that offers device login.
598
+
599
+ ### login-denied
600
+
601
+ The sign-in was denied on the approval page (`access_denied`). Nothing was
602
+ written, and an existing `~/.eris/yaks.json` is left as it was. Fix: run
603
+ `eris login` again if the denial was not intended.
604
+
605
+ ### login-expired
606
+
607
+ The device code expired before it was approved: the endpoint answered
608
+ `expired_token`, or the code's `expires_in` ran out locally. Nothing was
609
+ written. Fix: run `eris login` again and approve the new code in time.
610
+
611
+ ### login-env-token-overrides
612
+
613
+ A `WARN`, exit 0: `ERIS_YAKS_TOKEN` is set, and it wins over the token in
614
+ `~/.eris/yaks.json`, so `eris yak` keeps using the environment's token after
615
+ `eris login` (or still has one after `eris logout`). Fix: unset
616
+ `ERIS_YAKS_TOKEN` to use the file's token.
617
+
618
+ ## Adding a new slug
619
+
620
+ Call `emitFailureAndExit({ slug, what, fix, docs })` (or `reportFailure`, to
621
+ return 1 instead of exiting) from `ts/lib/eris-fail.ts` at the callsite (`docs`
622
+ defaults to `docs/ERRORS.md#<slug>`), then add a `### <slug>` heading here. A
623
+ plugin calls `renderFailureBlock` from the SDK (`src/cli/plugin.ts`). Slugs are
624
+ lowercase kebab-case, stable once shipped.
625
+
626
+ `tests/errors-doc.test.ts` asserts that every slug has a heading here, and it
627
+ **derives** the slug list from the raisers rather than carrying a copy: in the
628
+ `eris` shim, its `printf 'slug: …'` block; in every TypeScript tree (`src/`,
629
+ `ts/lib/`, `plugins/`), `slug: "…"` literals, the members of every `…Slug`
630
+ union, and `renderWarnLines(…, "<slug>")` arguments. So a new slug is covered
631
+ the moment it is raised — but it also fails the suite until its heading exists,
632
+ which is the point.