Skip to content

test(golden): byte-lock tau explain/version --check/bug/upgrade success+offline+failure surfaces - #245

Open
javimosch wants to merge 61 commits into
masterfrom
mago/task-244
Open

javimosch wants to merge 61 commits into
masterfrom
mago/task-244

Conversation

@javimosch

Copy link
Copy Markdown
Owner

Summary

Adds snapshot (golden) coverage for the four maintenance commands — tau upgrade, tau bug, tau explain, and tau version --check — across success, offline, and failure paths. The smoke-group coverage for these commands already shipped with each feature PR (#227, #229, #242, #243); this PR byte-locks their deterministic stdout/stderr/exit contract on top of it.

To test all four commands on one branch, the PR union-merges the three open feature chains:

All merge conflicts were additive (help text, Action enum, group registry, completion scripts, docs) and resolved by union.

What changed

  • New golden-maintenance smoke group — 28 snapshot cases (84 checks: exit + stdout + stderr each), regenerated via GOLDEN_UPDATE=1:
    • explain (8): catalog list, code + type-tag lookup, --human prose, uncataloged-code fallback, unknown name → exit 1, code 0 → exit 1, bad flag → exit 80
    • version --check (6): TAU_NO_UPDATE_CHECK opt-out, file:// stubs for newer/same/bad-payload/missing releases, --mode text
    • bug (5): unknown flag / --out+--no-report mutex / missing --title value → exit 80, and the deterministic --no-report JSON surface (default + custom title/labels/body)
    • upgrade (9): arg validation → 80, opt-out → 1, up_to_date, unconfirmed skip on non-TTY (stdin pinned to /dev/null), lookup + installer download failures, --dry-run
  • Normalization: release/installer stubs and the install dir live under $_GOLDEN_HOME so _golden_norm maps the file:// URLs and paths the commands echo to <HOME>. _golden_env now additionally scrubs TAU_UPDATE_URL, TAU_NO_UPDATE_CHECK, TAU_INSTALL_URL, TAU_INSTALL_DIR, TAU_BASE_URL, TAU_YES, BROWSER so ambient env can't leak into snapshots.
  • Union-merge fallout: explain added to fish + PowerShell completions (those files don't exist on the task-226 chain — required by the surface↔completions drift tests), section renumbering in AGENTS.md, and regenerated help/completions-* goldens for the merged surface.

tau bug's bundle-carrying output is intentionally not byte-pinned: the issue URL percent-encodes the diagnostics bundle (build metadata, curl version), so its bytes legitimately change every build. Its structure, redaction, and failure contract are covered by the bug smoke group.

How verified

  • zig build test — 486/486 unit tests pass (incl. the surface↔help/completions drift tests over the union)
  • ./scripts/smoke.sh — 1136 passed, 0 failed, 1 skipped (full offline suite)
  • ./scripts/smoke.sh --group golden-maintenance — 84/84 green in both GOLDEN_UPDATE=1 and compare modes
  • Golden diffs audited: regenerated help.stdout/completions-*.flags show only the expected bug/explain surface additions

Closes #244 (mago task #244)

mikayassa and others added 30 commits September 25, 2026 20:43
New subcommand writes ~/.config/tau/config.json as JSONC: it scans the
provider table's env_keys (plus TAU_API_KEY), selects the first provider
with a detected key (or an explicit --provider), and emits a commented
starter that names every provider's env vars -- never copying secret
values. Refuses to overwrite an existing file unless --force; --stdout
previews without writing.

configfile.load now strips // and /* */ comments before parsing so the
scaffolded file (and hand-commented configs) load cleanly; string-aware
scanning keeps "//" inside values (e.g. URLs) intact.

Also wires help/guide entries, bash/zsh completions, an offline smoke
group, and fixes a stray literal backslash before the Examples header in
--help output.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
docs/troubleshooting.md maps each user-visible error message (by exit
code and envelope type) to its cause and fix. To make the doc
discoverable, every {"err":...} envelope now carries a "docs" field
pointing at the published URL — the constant lives in version.zig as the
single source of truth. README's Troubleshooting section links the doc
and the embedded guide's see_also lists it.
Unit-test run() end-to-end against a hermetic tmp HOME (same convention
as configfile.zig's TestHome): fresh write produces a commented config
that loads without warning, existing configs are refused without --force
(contents untouched), --force regenerates the scaffold, --stdout writes
nothing, and a missing HOME exits 110.
…134)

Pin the user-visible error contract so DX regressions are caught in CI:

- golden test asserting formatErrorJson output byte-for-byte, including
  the "docs" link to the troubleshooting FAQ
- source scan asserting every hand-rolled {"err":...} literal in
  main/agent/acp/fleet/helpers keeps a "docs" field
- doc tests asserting docs/troubleshooting.md indexes every exit code,
  embeds its own canonical URL, and is linked from the README
Extend shell completion support beyond bash/zsh:

- completions/tau.fish — fish completion with subcommand-aware context
  helpers, per-candidate descriptions, and @file injection that strips
  the sigil, delegates to the builtin file completer, and re-adds '@'
- completions/tau.ps1 — PowerShell Register-ArgumentCompleter script
  (PS 5.1+/pwsh 7+) with tooltip descriptions, quoted '@file' handling
  (bare '@x' is splat syntax in PowerShell), and a Get-ChildItem
  fallback when CompletionCompleters is unavailable
- completions/tau.bash, completions/_tau — parity fixes: add the missing
  'guide' subcommand and 'calculator' tool, and offer fleet flags before
  the sub-subcommand (valid per args.zig ordering)
- completions/README.md — fish/pwsh install docs, file/shell matrix,
  the PowerShell @file quoting caveat
- scripts/smoke.sh — new offline 'completions' group asserting every
  --help-json flag, subcommand, and enum value appears in all four
  completion files (drift guard)
- README.md + CONTRIBUTING.md — point to completions/

Verified: fish 3.7 and pwsh 7.4 exercised end-to-end via
complete --do-complete / TabExpansion2; bash tested via COMP_WORDS
simulation; zsh via zpty. 274/274 zig tests, 13/13 completions smoke
checks, no regressions in help/flags/fleet-flags groups.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Read-only diagnostic that prints the fully merged config (config file ->
env vars -> CLI flags) as JSON, exactly as a run would see it. Unlike
acp/fleet, `config show` falls through the generic flag parser so override
flags (--provider/--model/--api-key/--mode/...) are reflected in the
output, which makes it useful for previewing merges without running.

Secrets are never printed: `api_key.value` is masked to "***" + last 4 via
the new cfgmod.redactApiKey, and `api_key.source` reports which precedence
level supplied the key (--api-key / config keys[<provider>] / env <VAR> /
config api_key / env TAU_API_KEY / builtin) via resolveApiKeyInfo, which
resolveApiKey now delegates to. The per-provider `keys` map is emitted
sorted with each value masked. config_file reports the probed path,
whether a file was read (new Config.config_path set by configfile.load),
and any parse warning; endpoint_source attributes the endpoint to the
provider table or env:TAU_ENDPOINT.

Bare `tau config` and unknown subcommands exit 80; `config show` rejects
positional arguments. Non-finite floats (e.g. --temperature nan) serialize
as null to keep the JSON valid.

Tests: 293/293 zig build test green (resolveApiKeyInfo source attribution,
redactApiKey, args parsing, formatEffectiveConfig). New offline smoke
group `config-show` (22 checks) covers merge precedence, redaction, error
paths, and env attribution. Docs updated: help_text, guide commands,
README, docs/configuration.md, AGENTS.md, CHANGELOG, and bash/zsh
completions.

Closes #140

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…able install.sh

Adopters shouldn't need a Zig toolchain to try tau. Push a v* tag and the
release workflow cross-compiles all four targets (linux/macos x86_64+aarch64)
from a single Linux runner, publishes tau-<os>-<arch>.tar.gz assets plus
SHA256SUMS.txt, and creates the GitHub release via gh CLI (no third-party
actions beyond checkout/artifact upload). A guard step fails the run when the
tag doesn't match src/version.zig; suffixed tags publish as pre-releases.
workflow_dispatch runs build-only for validation.

install.sh (POSIX sh) detects OS/arch, resolves the latest or --version-pinned
asset URL, verifies the checksum when sha256sum/shasum is available, and
installs to ~/.local/bin. --dry-run and TAU_OS/TAU_ARCH overrides make the
platform mapping testable offline, exercised by a new 27-check 'release' smoke
group that also drift-guards the workflow file.

Verified: all 4 targets cross-compile with zig 0.16.0; 27/27 release smoke
checks green; README + CHANGELOG updated.
Adds a smoke job to release.yml that runs after build and gates release:
each dist tarball is staged behind a local HTTP stand-in for the GitHub
releases URL, installed end-to-end through install.sh on the matching
runner (ubuntu-latest, ubuntu-24.04-arm, macos-15-intel, macos-latest),
and verified with tau --version/--help. Covers all four shipped assets.

install.sh gains a documented TAU_BASE_URL env override so the installer
can be pointed at any base URL exposing the releases layout — this is
what the smoke job (and local smoke tests via file://) use.

Fixes a latent checksum-verification bug the e2e check surfaced: the
release job generated SHA256SUMS.txt with ./-prefixed filenames while
install.sh greps " <asset>$" (space before the name), so no line ever
matched and every real install would abort. The workflow now emits clean
filenames and the installer grep tolerates an optional ./ prefix.

Verified: release smoke group 49/49 green, including a full offline
file:// install (download → checksum → extract → install → version/help)
and a checksum-mismatch rejection. release.yml parses as valid YAML.
CI adopters can now install tau with a single `uses: javimosch/tau@<ref>`
line. The composite action shells out to the repo's own install.sh via
$GITHUB_ACTION_PATH (so installer version matches the action ref),
exposes version/install-dir/base-url inputs mapped to the installer's
TAU_* env overrides, appends the install dir to $GITHUB_PATH, and
surfaces tau-path + version step outputs.

release.yml gains a smoke-action job that stages the linux-x86_64
tarball behind a local HTTP stand-in and runs `uses: ./` end-to-end
(download → checksum → install → outputs/PATH checks), gating the
release publish on the action working as advertised.

scripts/smoke.sh's release group pins the action surface: composite
type, install.sh invocation, all three inputs and their env mapping,
GITHUB_PATH/GITHUB_OUTPUT wiring, outputs, and a conditional YAML parse.

Verified: release smoke group 69/69; action run block exercised
offline against a staged file:// release tree (install + outputs
correct); action.yml and release.yml parse as YAML.

Stacked on mago/task-147 (install.sh + release smoke job, PRs
#146/#150) — this PR includes those commits until they merge.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Validates the whole setup chain in one JSON report on stdout:
config file parse (missing vs invalid vs unknown provider),
provider/model/endpoint resolution, API key provenance (source only —
never the key), curl on PATH, endpoint reachability (~5s probe), and an
optional --deep authenticated probe (max_tokens=1). --offline marks
network checks "skip". Exit 0 when no check fails, 1 otherwise; unknown
args exit 80.

An unknown provider is carried into the report as a failed
provider_resolution check instead of exiting 80, so doctor stays useful
exactly when setup is broken.

Supporting changes: Config gains doctor_offline/doctor_deep/
doctor_bad_provider plus config_path (set by configfile.load when a file
is read); resolveApiKeyInfo exposes key provenance (resolveApiKey now
delegates to it — same precedence contract, existing tests unchanged).

9 new unit tests in doctor.zig, 4 in args.zig (288/288 green); new
'doctor' smoke group (33 offline checks); help_text Diagnostics section;
AGENTS.md cheatsheet + file map; README/docs/configuration.md/CHANGELOG
pointers.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…um chain in release.yml

install.sh previously warned-and-continued when SHA256SUMS.txt was
unreachable or no sha256sum/shasum existed — a silent trust path for
tampered or corrupted binaries. Verification is now mandatory: the
install aborts when the manifest can't be fetched, the asset isn't
listed in it, no checksum tool is available, or the digest mismatches.
TAU_SKIP_CHECKSUM=1 is a documented opt-out for air-gapped mirrors.

release.yml closes the loop end-to-end: the build job emits a
per-asset .tar.gz.sha256 alongside each tarball, both smoke jobs verify
it against the downloaded artifact before staging, and the release job
re-checks SHA256SUMS.txt before publishing so a malformed manifest can
never ship (it would brick every install under fail-closed semantics).

Smoke release group grows 49 -> 83 checks: drift guards for the new
workflow steps plus e2e cases for manifest-miss, unlisted-asset, and
the skip-checksum bypass. README + CHANGELOG updated.
…codes

Adds the coverage issue #165 asks for on top of the `tau doctor`
implementation (PR #160 / mago/task-127):

- zig: checkConfigFile now exercised through real file I/O via a throwaway
  HOME (missing file, valid file, invalid JSON, unreadable/dir-as-file),
  checkEndpoint and checkAuthProbe failure paths pinned against a refused
  local port, keyHint/providerListHint contents, ok:true report shape with
  warnings, and curlHttpCode's spawn-failure null.
- smoke: doctor group 33 -> 63 checks — curl missing from PATH cascade
  (--deep so both network checks report the curl gate), warn-doesn't-fail
  exit 0, positional-arg exit 80 with stderr message, and a local python
  stub server driving endpoint_reachable (401 counts as reachable) plus
  --deep auth_probe for HTTP 200/401/429 including a key-material leak
  assertion.
- CHANGELOG: Tests section entry for #165.

Verified: zig build test 297/297, smoke doctor group 63/63.
--debug already streamed [DEBUG] lines to stderr; it now also mirrors them
to ~/.config/tau/debug/<UTC-timestamp>-<suffix>.log (TAU_DEBUG_LOG overrides)
so users can attach the file to bug reports. New src/debuglog.zig owns the
log: it masks the resolved API key plus every other configured/env key,
Bearer tokens, api_key-style JSON fields, and *_KEY=/_TOKEN= env-style
assignments (***REDACTED***); stderr keeps the raw output. The file is
created owner-only (0600) with a provenance header (version, provider,
model, endpoint, key_resolved) and each entry is capped at 8 KiB.

Error envelopes gain an optional "debug_log" field linking the active log
(formatErrorJson + the /goal-session literal emitter — the only literal on
the .run path where a log can exist), and startup emits {"debug_log":...}
on stderr. Failure to create the file degrades to a warn envelope; the run
continues on stderr-only debug output.

Also makes the help text's "perf stats" claim real: a [DEBUG] perf line
(tokens_out, elapsed_ms, exit_code) is emitted once per run.

Stacked on mago/task-130 (PR #131) — both touch the err envelope format;
stacking keeps the docs field consistent.

Closes #167

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ediation hints

Every {"err":...} envelope now carries code, type, message, recoverable,
a remediation hint, and the docs link — emitted from one shared formatter
(src/errors.zig) instead of six hand-rolled literals across main/agent/acp/
fleet/helpers.

- errors.zig: canonical Spec registry (codes 1/80/82/105/106/110/111),
  specFor() lookup, format()/printErr()/printOut() emitters that splice in
  debuglog's debug_log field, and an OOM fallback that keeps the shape.
- Codes and wire `type` tags are documented as stable (never renumbered or
  renamed) in a new "Stable error catalog" table in docs/troubleshooting.md.
- Auth failures keep their dynamic hint (names the provider env var); all
  other codes fall back to the catalog hint.
- Snapshot tests pin every catalog entry byte-for-byte; sync tests assert
  the catalog covers docs/troubleshooting.md and --help-json exit_codes;
  the literal-emitter source scan now also requires the hint field.
- Smoke: envelope assertions now check recoverable/hint/docs (82-path gated
  on has_key since fleet's auth check fires first in keyless envs).

Stacked on mago/task-167 (PR #168) — it owns the current envelope shape
(docs + debug_log fields). docs/integration.md (PR #145) already declares
new fields non-breaking; its "minimal {code,message} envelope" line goes
stale once both land and should be updated there.

Closes #171

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…stable tags

Adds a `homebrew` job to the release workflow that regenerates
Formula/tau.rb from the published SHA256SUMS.txt manifest (new
scripts/generate-homebrew-formula.sh, runnable standalone) and pushes it
to javimosch/homebrew-tap via the contents API, so macOS/Linux users can
`brew install javimosch/tap/tau` and track releases with `brew upgrade`.

Pre-release tags (v*-*) are skipped so the tap only tracks stable
versions, and the publish step warns + skips when the HOMEBREW_TAP_TOKEN
secret is absent rather than failing the release.

Docs: README gets a Homebrew install option, release notes mention brew,
CHANGELOG + AGENTS.md file map updated. Smoke release group grows to 118
checks covering the generator (render, v-strip, fail-closed paths) and
workflow drift guards.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
- New docs/providers.md: per-provider credential setup (env vars, config
  keys map, endpoints, default models), the 6-level key-resolution chain,
  and exit-106 auth troubleshooting (missing key, silent keyless exit,
  rejected key, shadowed TAU_API_KEY, TAU_ENDPOINT auth mismatches).
- errors.zig: Spec gains a `docs` field (default: troubleshooting URL) and
  Opts a docs override; the auth_failed spec points envelopes at
  providers.md so every code-106 error links the credential guide.
- version.zig: providers_doc_url constant next to troubleshooting_doc_url.
- Cross-links: troubleshooting.md (example envelope, catalog docs column,
  auth section), README quickstart + troubleshooting table,
  configuration.md Providers section, `tau guide` see_also.
- Pins: golden snapshot updated; new tests pin the 106 -> providers.md
  link and that providers.md documents every provider's name, env keys,
  endpoint, and default model; smoke dry-run group gains a keyless
  fleet-run envelope check.
…re-commit

Adds a `validate` subcommand under `tau config` (stacked on the #142 `config`
parent). `tau config validate [path]` checks a config file — default
~/.config/tau/config.json — and prints a report to stdout:
{"path":..,"ok":bool,"errors":[{key,message}]} in JSON mode or one
"path: error in \"key\": message" line per problem in --mode text.

Checks are derived at comptime from configfile.FileConfig (now pub), so the
validator can't drift from the loader's schema, plus value-level checks the
struct can't express: unknown keys, type mismatches, unknown provider/mode,
compact_threshold outside 0-1, negative temperature, negative/overflowing
integers, and keys.* entries that aren't strings or name an unknown provider.
Syntax errors get a line/column via std.json.Diagnostics; key values are never
echoed. Exit codes stay inside the pinned contract: 0 valid, 1 problems found,
80 CLI misuse. Fully offline — no network, no env probing beyond HOME.

Coordinated points: JSONC comment support (PR #128) lands on the loader later —
when it does, validate should strip comments the same way; unknown-key
warnings (PR #153) become hard errors here by design.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…Windows smoke

Adds the Windows install path end-to-end:

- release.yml: new x86_64-windows-gnu build target shipping
  tau-windows-x86_64.zip (Windows convention); SHA256SUMS.txt now covers
  .zip assets; new smoke-windows job installs the zip through install.ps1
  on a native windows-latest runner (file:// TAU_BASE_URL staging) and
  verifies tau.exe --version/--help before the release can publish.
- install.ps1: mirrors the install.sh contract — -Version/-Dir/-DryRun/
  -Help (both -name and --name forms, manual $args parse since pwsh does
  not bind --switches), TAU_VERSION/TAU_INSTALL_DIR/TAU_ARCH/TAU_BASE_URL/
  TAU_SKIP_CHECKSUM overrides, fail-closed Get-FileHash SHA256
  verification, Expand-Archive install to %LOCALAPPDATA%\Programs\tau with
  a user-PATH note. throw/return instead of exit so irm|iex installs
  don't kill the caller's shell; file:// URLs resolve via System.Uri so
  the staging trick works on every pwsh host.
- scripts/generate-winget-manifest.sh: renders the three-file
  microsoft/winget-pkgs manifest (version/installer/defaultLocale) from
  the published checksums; the winget job validates it, uploads it as a
  workflow artifact, and auto-submits via wingetcreate when
  WINGET_PKGS_TOKEN is configured (warn-and-skip otherwise).
- scripts/generate-scoop-manifest.sh: renders bucket/tau.json for
  javimosch/scoop-bucket; the scoop job publishes via gh api contents PUT
  behind SCOOP_BUCKET_TOKEN, warn-and-skip while the token is unset or the
  bucket repo does not exist.
- smoke.sh release group: +install.ps1 surface checks (pwsh-gated),
  workflow drift guards for the new jobs, generator contract tests, and a
  file:// install.ps1 e2e exercising the full fail-closed checksum ladder.
- README quickstart + release notes + CHANGELOG document the Windows path.

Stacked on mago/task-175 (the release chain this extends).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…tor)

`tau setup` runs the full first-run flow as one NDJSON transcript:
scaffolds ~/.config/tau/config.json (init semantics — keeps an existing
file instead of failing), installs shell completions for the $SHELL-
detected shell (--shell bash|zsh|fish|pwsh|all overrides), and embeds the
`tau doctor` health report, ending with a {"step":"setup","ok":...}
verdict. Exit 0 when every step is ok/skipped, 1 on any failure.
Idempotent: rc/profile edits carry a "# tau completions" marker so
re-runs don't duplicate lines.

- build.zig generates a `completions` module embedding completions/*
  via addWriteFiles, so `tau setup` installs scripts without a checkout
- doctor.zig: split collectReport() out of run() for reuse; fix
  config_file check to strip JSONC comments (init scaffolds parse ok now)
- --provider/--model/--api-key/--offline/--deep pass through to the
  doctor step; unknown providers ride in as a failed check (doctor
  convention) while init falls back to detection
- completions: add init/doctor/setup coverage to bash, zsh, fish, pwsh
- 6 new unit tests + 41-check smoke group; docs: README, AGENTS.md,
  CHANGELOG, completions/README, docs/configuration.md

Closes #181
…ind confirmation

`tau doctor --fix` plans and applies remediations for fixable check
outcomes: scaffold a missing config (init semantics, creates ~/.config/tau),
move an unparseable config to config.json.bak and re-scaffold, tighten POSIX
permission bits (0700 dir / 0600 file — the file can hold API keys), and
install/refresh shell completions. Every fix requires confirmation: --yes
(also -y, implies --fix) applies all non-interactively, a TTY stdin gets a
per-fix y/N prompt on stderr, and anything else is reported as skipped. The
emitted report gains a "fixes" array ({id,check,status,detail}) and reflects
the post-fix state when anything was applied; the exit code follows the
final report.

Two new always-on diagnostics back the remediations: config_permissions
(warn on group/other access, POSIX-only) and shell_completions (warn on
missing/outdated scripts or absent rc hooks for the detected/--shell shell).

Also exports the completion install helpers in setup.zig (installCompletion,
completionState, resolveShells, rc/script accessors) so doctor and setup
share one code path, and completes the new flags in all four shell scripts.

Verified: 324/324 zig tests (8 new covering planning, confirmation, every
fix kind, and round-trips), 84/84 doctor smoke checks including the new
--fix sections (skip-without-confirm, apply-with---yes, .bak restore),
setup/init/completions groups green, x86_64-windows-gnu cross-compile.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…trix

Adds .github/workflows/ci.yml which runs `zig build test` and both offline
smoke suites on a native runner for each released platform (linux
x86_64/aarch64, macos x86_64/aarch64, windows x86_64) on PRs, master pushes,
and v* tags — so platform regressions fail before a release is cut.
`workflow_call` is exposed so the release workflow can gate on the matrix.

Portability fixes to make that matrix green:

- args.zig: parse() now takes a plain argv slice instead of
  std.process.Args, whose POSIX-only `.vector` field broke `zig build test`
  compilation for windows-gnu. main.zig materializes the platform argv
  (WTF-16 on Windows, UTF-8 on POSIX) into a UTF-8 slice itself.
- smoke.sh: the acp group now pins both documented daemon contracts —
  exit 0 + running:false on POSIX, exit 111 err envelope on Windows —
  instead of failing on the Windows leg.
- smoke.sh + smoke-features.sh: pass --api-key fake to checks exercising
  post-auth validation (fleet run without --goal, /goal without --session),
  so they are deterministic in keyless CI environments — key resolution
  precedes those checks and would exit 106 first.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
--json replaces the single-object report with line-delimited events matching
the documented stream contract: {"check":{...},"done":false} per check as it
finishes, {"fix":{...},"done":false} per remediation under --fix, and a
terminal {"ok","version","summary","fixes"?,"done":true} verdict. A check
re-diagnosed after an applied fix re-emits with its post-fix status. Exit
codes, the default report shape, and `tau setup`'s embedded report are
unchanged.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
mikayassa and others added 26 commits October 15, 2026 11:16
Adds a doc-only `cause` field to the Spec registry in src/errors.zig so
the catalog is the single source of truth for code, type, cause, hint,
and guide link. scripts/gen-errors-doc.py (stdlib-only) renders
docs/errors.md — envelope shape, the full catalog table, and the silent
exit paths — and `--check` regenerates in memory and fails on drift.

Wired in twice: a new `errors-doc` smoke group runs --check and proves
it is non-vacuous, and a zig test asserts every row, hint, and cause
appears in the generated doc verbatim. troubleshooting.md and README
link the new page.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
… bundle

Stacked on mago/task-204 (doctor --json/--fix chain + version.build +
debuglog).

`tau doctor --report` prints a Markdown bundle for pasting into a GitHub
issue: verdict line, build metadata table (version/commit/target/build
date/zig), the checks table, a Fixes table when --fix ran, effective
settings (provider/model/endpoint/context_window + key source — never the
key), and the config file verbatim in a fenced jsonc block. The config
dump is passed through debuglog.redactAlloc with every credential source
collected (resolved key, --api-key, config api_key + keys.*, TAU_API_KEY,
all provider env vars, builtins), so key material can never leak into the
report. Paths stay visible, matching the --debug log convention.

--report + --json is rejected at parse time with exit 80 (mutually
exclusive). Exit codes and the default JSON report are unchanged.

debuglog.zig: registerSecrets refactored onto a new pub collectSecrets so
the same source enumeration serves both the --debug log and --report.

Also: help_text + guide commands row, all four completion scripts +
completions README, troubleshooting.md diagnostic-toolkit row, unit tests
(formatReportMarkdown shape/redaction, appendMdCell escaping, arg parse +
mutex), and 19 new offline smoke checks in the doctor group.

368/368 zig tests, 113/113 doctor smoke checks green.
…n save

`tau config edit [path]` opens the config file in the user's editor
(TAU_EDITOR > VISUAL > EDITOR > vi/notepad, resolved through the system
shell like git, so `EDITOR="code --wait"` works), then runs the same
checks as `config validate` when the editor exits. A missing file is
seeded with the commented `tau init` scaffold first.

On a clean save the report is {"ok":true,"path":...,"editor":...,
"created":bool,"valid":true} (or "<path>: ok" in text mode). On failure
the validation report goes to stdout with exit 1 — the edits stay on
disk so the user can rerun and fix. Editor spawn/non-zero exits emit an
editor_failed envelope (exit 1); misuse exits 80.

Did-you-mean hints land in the shared validator: misspelled config keys,
bad `provider` values, and unknown `keys.<provider>` names now suggest
the nearest candidate (edit distance <= 2, via the suggest.zig helper
task-200 also uses — vendored verbatim so the chains merge cleanly).
The bad-subcommand error for `tau config <x>` gets the same treatment.

Completions (bash + zsh), docs/configuration.md, AGENTS.md, CHANGELOG,
help text, and `tau guide` updated. New `config-editor` smoke group
(18 checks, stubbed TAU_EDITOR); 343/343 unit tests pass.
… + credential sweep

--probe emits two rows per registry provider: probe_<name> (endpoint
reachability — any HTTP response counts) and probe_<name>_auth (key resolved
with the same precedence a run configured for that provider would use, then a
one-token authenticated POST when a key is found). Only the key's source
label is ever reported — never the key itself. The configured provider probes
its resolved endpoint/model (TAU_ENDPOINT/--model honored); auth rows skip
when the endpoint probe failed, when offline, when curl is missing, or when
no credential resolves. Rows stream as NDJSON check events under --json and
appear in the --report checks table. Without --probe a single skipped
provider_probes row advertises the sweep.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…ng onboarding

Adds a "clients" step between completions and doctor in `tau setup`:

- `--client zed|vscode|jetbrains` writes a minimal settings file
  (agent_servers.tau / acp.agents.tau) with the absolute path of the
  running binary, so GUI editors that lack PATH lookups still resolve it.
- Existing settings files are never rewritten — they're JSONC and a
  parse-and-rewrite would destroy comments. If a "tau" entry is already
  present (checked via JSONC-aware parse) the entry reports
  already_installed; otherwise a paste-ready fragment lands in
  ~/.config/tau/snippets/<client>.jsonc and the entry reports "manual".
- `--client nvim` always reports manual: Neovim ACP plugins are Lua
  config, so setup writes ~/.config/tau/snippets/nvim.lua with both the
  avante.nvim and CodeCompanion blocks plus an editor_detected flag.
- Without --client the step is skipped and only reports which editor
  config dirs exist; detection never writes.
- Manual outcomes pass the setup verdict; only IO failures fail it.

Wired: args.zig enum validation (with "code" alias), Config.setup_client,
help text, guide row, all four completion scripts, AGENTS.md, README,
CHANGELOG, and a docs/acp.md shortcut note. 7 new unit tests cover
parsing, paths, JSONC-aware tau-key detection, and the three write
outcomes; 30 new offline smoke checks cover the flag end-to-end
(324/324 unit, 73/73 setup group).

Closes #219

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
# Conflicts:
#	AGENTS.md
#	CHANGELOG.md
#	completions/README.md
#	completions/_tau
#	completions/tau.bash
#	scripts/smoke.sh
#	src/args.zig
#	src/config.zig
#	src/configfile.zig
#	src/main.zig
# Conflicts:
#	CONTRIBUTING.md
#	scripts/smoke.sh
#	src/main.zig
…, config edit)

Adds a golden-onboarding smoke group that byte-locks the whole first-run
surface end-to-end on top of the task-208 golden harness:

- tau init: fresh/rerun/--force/--stdout/--provider/detected-key/no-HOME
  runs; exit codes, stdout envelopes, and the generated config.json
  scaffold are pinned byte-exact
- tau setup --offline: the full 5-line NDJSON step transcript (init,
  completions, clients, embedded doctor report, verdict) for fresh and
  re-run flows, keyless failure, --client zed/nvim plus the existing-file
  "manual" path; generated settings.json, snippets, .zshrc, and the
  installed completion scripts are pinned
- tau config edit: TAU_EDITOR-stubbed runs covering a valid save
  (json + --mode text), an invalid save (validation report on stdout,
  edits stay on disk), editor failure (scaffold seeded first,
  editor_failed envelope), and the no-HOME arg error

New helpers: golden_check_n (golden_check + normalization), golden_file
(snapshot a generated file), _golden_fresh_home (per-scenario isolated
HOME), and _golden_norm which rewrites the volatile bits — the temp HOME
path, the tau binary path embedded in client configs, and the curl
version in the doctor report — to <HOME>/<TAU>/<VER>.

Merges the chains the tested features live on: mago/task-212 (config
edit/set/get/unset/validate + init), mago/task-219 (setup --client +
doctor + init), and mago/task-208 (golden harness).

Verified: 383/383 zig unit tests; golden-onboarding 78/78 checks green and
deterministic across runs; golden-errors 78/78 unchanged; full offline
suite 510 passed with only the 17 pre-existing keyless-env failures
(identical to baseline). Regenerate with:
GOLDEN_UPDATE=1 ./scripts/smoke.sh --group golden-onboarding

Closes #223
…check against a loopback stub

Adds a `selftest` check row that spawns the running tau binary with a
fully scrubbed environment — every provider env key, TAU_API_KEY,
TAU_ENDPOINT and TAU_DEBUG_LOG stripped, HOME pointed at a throwaway
dir so no real config can leak in — plus a stub TAU_API_KEY and a
TAU_ENDPOINT aimed at an in-process loopback HTTP stub (std.Io.net,
no python/sh dependency, Windows-safe). The child must return the
canned marker end-to-end (one-shot, --no-tools --no-stream) for the
row to report ok; exit code and truncated stderr/stdout excerpts are
surfaced on failure.

The stub serves up to 4 connections (the provider HTTP layer retries)
and is reaped by a done-flag + self-connect kick so a blocked accept()
never deadlocks the check.

Deliberately not gated on --offline: the stub is loopback-only, so
`tau doctor --offline --selftest` is a fully hermetic install check.
Skips only when curl is unavailable (the child shells out to curl).

Wires --selftest through args parsing, Config, help_text, the guide
table, all four shell completions, smoke coverage (env-scrub and
isolation assertions included), README/AGENTS.md/troubleshooting/
configuration docs, and CHANGELOG.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
`tau explain [<code|type>] [--human]` prints a stable error's cause,
remediation hint, and docs link straight from the Spec registry in
src/errors.zig — the same source gen-errors-doc.py renders
docs/errors.md from, so CLI output and the published catalog can never
drift apart.

- errors.zig: findSpec resolves decimal codes (uncataloged codes keep
  their code under the generic internal_error spec, mirroring the
  envelope rule; 0 is success and never an error) and case-insensitive
  type tags; formatEntryJson/formatEntryHuman serialize one entry.
- args.zig: new Action.explain parsed like acp/fleet/skills/guide.
- main.zig: `.explain` arm prints one entry or the whole catalog as
  {"errors":[...]}; unknown queries fail closed with a not_found
  envelope (exit 1), bad flags with invalid_argument (exit 80).
- Wired into --help, the embedded guide, and bash/zsh completions.
- docs/errors.md gains a "Reading this catalog offline" section via
  the generator; troubleshooting.md, README, CHANGELOG, and AGENTS.md
  point at the new command.
- New `explain` smoke group (31 checks); errors-doc drift check,
  308 unit tests, and help/guide/flags/acp/fleet groups all green.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Non-blocking, opt-out update check against the latest GitHub release:

- src/update.zig (new): curl-based lookup of the latest release tag
  (5s --max-time, same subprocess transport as doctor's probes), lenient
  dotted-version comparison (v-prefix + suffix tolerant), and a Result
  surface that reports checked:false + reason instead of failing.
- `tau version --check` merges the check into the version object:
  checked/latest/tag/update_available (+ update_url when newer) in JSON,
  key: value lines in --mode text, composes with --verbose. Always
  exits 0 — offline, missing curl, and bad payloads are reported, never
  fatal.
- `tau doctor` gains an advisory `update_available` row: warn when a
  newer release exists (never fail — a warn can't flip the verdict),
  ok when current, skip under --offline / TAU_NO_UPDATE_CHECK / no curl.
- Opt-out: TAU_NO_UPDATE_CHECK disables the check everywhere;
  TAU_UPDATE_URL overrides the lookup URL for proxies/mirrors and
  file:// stubs in tests.

Docs: help text, embedded guide, README, CHANGELOG, AGENTS.md §18,
configuration.md env table, troubleshooting toolkit table, and all four
completion scripts.

Verified: 387/387 zig unit tests; new `update` smoke group 31/31 and
related groups green (the 16 goal/fleet keyless-env failures are
pre-existing, exit-106-before-82 path, identical on the baseline).
…attended onboarding

tau setup never prompts and never reads stdin — the new flags make that
contract explicit for devcontainers, dotfiles scripts, and CI:

- `--yes`/`-y` approves all write steps; `--non-interactive` declares the
  unattended run. Both are recorded in the verdict as
  "non_interactive":true/"yes":true so scripts can assert on them.
- Env equivalents honored at parse time with flag > env > detect
  precedence: TAU_YES, TAU_NONINTERACTIVE (truthy = 1/true/yes/on),
  TAU_SETUP_SHELL, TAU_SETUP_CLIENT (validated like the flags; invalid
  values exit 80).
- CI is deliberately not auto-detected — scripts opt in explicitly.
- Shared validSetupShell/validSetupClient validators + envTruthy helper;
  5 new parse tests + an unattended() unit test.
- Help text, embedded guide, all four completions, README, CHANGELOG,
  AGENTS.md, and docs/configuration.md env table updated.
- 30 new smoke checks in the setup group: flag/env-triggered mode, flag
  beats env, falsy env ignored, invalid env -> 80, stdin-closed run.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
# Conflicts:
#	README.md
#	completions/tau.ps1
#	src/config.zig
#	src/main.zig
# Conflicts:
#	CHANGELOG.md
#	scripts/smoke.sh
# Conflicts:
#	CHANGELOG.md
#	scripts/smoke.sh
#	src/args.zig
…or --selftest)

Adds a `first-run` job to the ci workflow that locks the documented
first-run path for every released platform (linux x86_64/aarch64, macos
x86_64/aarch64, windows x86_64):

- builds tau, stages it as a local release tree
  (srv/latest/download/<asset> + SHA256SUMS.txt — the exact layout the
  installers fetch), and installs it through the shipped installer —
  install.sh on POSIX legs, install.ps1 on the Windows leg — against a
  file:// TAU_BASE_URL so nothing touches the network;
- then drives scripts/e2e-first-run.{sh,ps1} against the INSTALLED binary
  in a scratch HOME with every credential source scrubbed:
  `tau setup --yes --offline` (unattended onboarding: config scaffolded,
  verdict ok/yes/non_interactive), `tau doctor --offline --selftest`
  fully credential-free (asserts the selftest check row reports ok — the
  feature's promise — while the keyless verdict honestly exits non-zero),
  and the same doctor run with a fake resolvable key (asserts exit 0).

The release workflow's install-smoke jobs now assert the same selftest
check row on the shipped artifacts, so published binaries are verified to
self-test too. A new `first-run` smoke group pins the drivers, the ci.yml
wiring, and runs the POSIX driver end-to-end against zig-out/bin/tau.

Branch carries the prerequisite chain merges: mago/task-222 (doctor
--selftest), mago/task-230 (setup --yes/--non-interactive), mago/task-182
(install.sh + install.ps1 + release.yml), mago/task-193 (ci matrix).

Verified: zig build test 392/392, full offline smoke 625/625, and the
complete install.sh file:// → e2e-first-run.sh chain run locally.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…the registered surface (task #236)

Adds a golden-help smoke group that byte-locks `tau --help`, `tau -h`, and
`tau --help-json` (version normalized to <VER>), plus a sorted --flag
listing extracted from each shipped completion script
(tests/golden/completions-<shell>.flags).

Four unit tests in main.zig pin the structural contract both ways: every
subcommand (`eq(argv[0], "…")`) and flag (`eq(…, "-…")`) literal extracted
from args.zig plus flag_specs must appear in help_text and in all four
embedded completion scripts; every --flag token a script offers must map
back to a registered flag; and every `tau <sub> <action>` documented in
help_text must be a real subcommand with a completable action. Verified
both directions by injecting a bogus flag (both tests fail) and removing
one (forward test fails).

The merge of the task-233 and task-223 chains surfaced real drift the new
checks now pin: completions lacked the `config` subcommand (fish/ps1), the
`-xt` alias (zsh), `--raw` (bash/ps1), and `-y` (doctor). _golden_norm
also normalizes the doctor report's build block (commit/target/date/zig)
so onboarding goldens stay deterministic across builds.

Regenerated golden-errors/golden-onboarding snapshots: the merged chain's
envelopes now carry hint+docs and the embedded doctor report grew build
metadata and the probe/selftest/permissions/completions checks.

455/455 unit tests; 898/899 offline smoke checks (1 skipped).
…ll.sh

Reuses the `version --check` release lookup (update.zig), confirms on a TTY
(--yes/-y or TAU_YES=1 approves non-interactively), then downloads and execs
the release tag's own install.sh — so upgrades go through the same
fail-closed SHA256-verified install path as fresh installs. --dir overrides
the destination (default: the running binary's directory; env
TAU_INSTALL_DIR), --dry-run prints the resolved plan, and TAU_INSTALL_URL
overrides the installer script URL for mirrors/tests. Emits one JSON report
with a status field; POSIX-only — Windows exits 111 pointing at install.ps1.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…octor --report bundle

`tau bug` runs the same diagnostic sweep as `tau doctor` (--offline/--deep
and the --provider/--model/--api-key/--shell overrides all apply, unknown
providers ride into the bundle as a failed check) and renders the redacted
Markdown bundle via doctor.formatReportMarkdown, then emits one JSON object:
{"title","labels","url","body","report","url_truncated","out","opened","done":true}.

- url is a github.com/javimosch/tau/issues/new link with title/body/labels
  percent-encoded into the query; body always carries the full issue text so
  agents can also file via the API
- bundles that would push the URL past ~7KB fall back to a short link body
  (url_truncated:true) — the JSON fields keep everything
- --title/--body/--labels pre-fill the issue; --open launches the URL via
  $BROWSER or the platform opener (open/xdg-open/rundll32) and degrades to
  opened:false; --out also writes the bundle to a file; --no-report omits
  diagnostics (mutually exclusive with --out)
- version.zig gains issues_new_url; doctor.formatReportMarkdown promoted to
  pub for reuse; exit 0 on success (the verdict lives inside the bundle)

Wired through args.zig (Action.bug + flag parsing + mutex validation),
config.zig (bug_* fields), main.zig dispatch/help/guide, all four
completion scripts, README/troubleshooting/AGENTS/CHANGELOG docs, a new
49-check `bug` smoke group, and unit tests for URL encoding, body
composition, and the JSON envelope (378 zig tests green).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
# Conflicts:
#	AGENTS.md
#	README.md
#	completions/README.md
#	completions/tau.bash
#	completions/tau.fish
#	completions/tau.ps1
#	docs/troubleshooting.md
#	scripts/smoke.sh
#	src/args.zig
#	src/config.zig
#	src/main.zig
#	src/version.zig
# Conflicts:
#	AGENTS.md
#	CHANGELOG.md
#	README.md
#	completions/README.md
#	completions/_tau
#	completions/tau.bash
#	scripts/smoke.sh
#	src/args.zig
…ces (task #244)

Union-merges the three unmerged feature chains (mago/task-240 upgrade +
version --check, mago/task-241 bug, mago/task-226 explain) so all four
maintenance commands can be snapshot-tested on one branch, then adds a
`golden-maintenance` smoke group: 28 cases pinning exit status + exact
stdout/stderr for success, offline, and failure paths.

- explain: catalog list, code/type-tag lookup, --human prose, uncataloged
  fallback, unknown name (1), code 0 (1), bad flag (80)
- version --check: opt-out, newer/same/bad-payload/missing file:// stubs,
  --mode text
- bug: bad flag/mutex/missing-value envelopes (80) + the deterministic
  --no-report output (the bundle-carrying URL is not byte-pinned — it
  percent-encodes build metadata; structure is covered by the bug group)
- upgrade: arg validation (80), opt-out (1), up_to_date, unconfirmed skip
  (stdin </dev/null), lookup/installer failure, --dry-run

Stubs and install dirs live under $_GOLDEN_HOME so _golden_norm maps them
to <HOME>; _golden_env now also scrubs TAU_UPDATE_URL, TAU_NO_UPDATE_CHECK,
TAU_INSTALL_URL, TAU_INSTALL_DIR, TAU_BASE_URL, TAU_YES, BROWSER.

Also completes the union's completion surface (explain added to fish + ps1,
whose files don't exist on the task-226 chain) and regenerates the
help/completions goldens for the merged surface.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-19T20:21:08.236824Z 5665da6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9597215b-6beb-47b0-bd03-7207d988fbf8
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add smoke and snapshot tests for tau upgrade, tau bug, tau explain, and tau version --check covering success, offline, and failure paths

2 participants