Skip to content

Choose redaction with --redact=WHEN, and test completions by running them - #10

Merged
kjanat merged 6 commits into
masterfrom
feat/redact-when-modes
Aug 10, 2026
Merged

Choose redaction with --redact=WHEN, and test completions by running them#10
kjanat merged 6 commits into
masterfrom
feat/redact-when-modes

Conversation

@kjanat

@kjanat kjanat commented Aug 10, 2026

Copy link
Copy Markdown
Owner

The banner is gone

envctl env opened with # envctl v0.5.0 (redacted). That suffix already means
something in this tool: src/diff.c writes +++ .env (redacted), where the
thing before the parenthesis is the subject whose content is hidden. In the
banner the thing before it was the version string, so the line read as though
the version had been masked. Nothing in it ever was.

It also took the first line of what is otherwise a pure KEY=VALUE stream, so
envctl env | head -1 returned a comment instead of a variable, and every
consumer had to know to skip a leading #. crontab -l made the same mistake
and broke crontab -l | crontab -.

Four env-cmd-* cases only shelled out to git describe because of the version
in that line. They are byte-literal again.

--redact takes a WHEN

Masking was a tri-state written as two opposing booleans. Every tool with
terminal-conditional output uses one flag with values instead:
--color=always|auto|never in ls, grep, diff, git, rg, ip, dmesg.

Three values would not have covered this one. want_redact evaluates two
independent signals, and agent detection is a module of its own with seventeen
cases behind it, so auto alone would bury that axis:

--redact= Masks when
never never
auto an agent is detected and stdout is a terminal
agent an agent is detected, terminal or not
tty stdout is a terminal, agent or not
always always

auto stays the default, so pipe-agent-stays-raw.case still holds: an agent
reading through a pipe gets the real value, which is what keeps
TOKEN=$(envctl get TOKEN) working. That is a deliberate decision, and
--redact=agent is the setting for anyone who wants it closed. It had no
spelling before this.

A bare --redact still means always, so nothing existing changes.

--raw stops doing two jobs

It meant "never mask" and "never escape control bytes". ls keeps those apart,
--color for one and -N, -b, -q, --show-control-chars for the other.
Escaping moves to --show-control-chars under GNU's name, and --raw becomes
the shorthand for both.

--raw works on env now. Refusing it there bought no safety with env(1)
sitting next to it; it only cost the caller envctl's key filtering, sorting and
escaping.

Completions are tested by running them

The old coverage compared the generator against a recorded copy of its own
output. That cannot catch an emitter that puts correct data somewhere the shell
applies too widely, which is precisely what shipped: the zsh per-command arm
listed each command's flags correctly the whole time, while the top-level
_arguments kept its specs live past the command word, so
envctl env --redact --sort - offered --raw. The suite stayed green
throughout.

tests/completions.sh runs in make test. It asks bash and fish for their
candidate lists directly, drives zsh through zpty, and asks the binary whether
each flag is valid for that command, probing contexts including one where a flag
is already typed, which is where the fault lived. It was checked against a copy
of the script with the fault put back, and fails there.

On its first run it found another one: bash returned the shell list for
completions and module before it ever looked at flags, so
envctl completions - offered nothing at all.

Verification

  • make test: 250 cases plus the completion harness
  • zsh -n, bash -n, fish --no-execute on every regenerated script
  • Value completion driven live in zsh and fish for --redact=
  • Control-byte split checked with od -c, since cat -v renders an escaped
    caret and a real ESC identically
  • dprint fmt clean

Not in this change

The ten c->id == CMD_* branches in src/complete.c still encode facts the
registry does not carry. That refactor is #9. The harness above is what keeps
them honest until then.

…them

`envctl env` opened with `# envctl VERSION (redacted)`. The suffix means
something else in this tool, since `src/diff.c` writes `+++ .env (redacted)`
where the thing before the parenthesis is the subject whose content is
hidden, so the banner read as though the version string had been masked.
It also took the first line of a `KEY=VALUE` stream, which `head -1` and
every other consumer then had to skip. The line is gone.

Masking now reads as one flag with five values instead of two opposing
booleans, following `--color=always|auto|never` in ls, grep, diff and git.
Three values would not cover it: `want_redact` evaluates two independent
signals, and agent detection is a module of its own with seventeen cases
behind it.

    never    never
    auto     an agent is detected and stdout is a terminal
    agent    an agent is detected, terminal or not
    tty      stdout is a terminal, agent or not
    always   always

`auto` stays the default, so `pipe-agent-stays-raw.case` still holds: an
agent reading through a pipe gets the real value, which is what keeps
`TOKEN=$(envctl get TOKEN)` working. `--redact=agent` is the setting for
anyone who wants that closed, and it had no spelling before.

`--raw` did two unrelated jobs. ls keeps them apart, `--color` for one and
`--show-control-chars` for the other, so control-byte escaping moves to a
flag of its own under GNU's name and `--raw` becomes the shorthand for
both. It also works on `env` now: refusing it there bought nothing with
`env(1)` sitting next to it.

The completion tests only compared the generator against a recorded copy
of its own output, which cannot catch an emitter that puts correct data
where the shell applies it too widely. That is exactly what shipped: the
zsh per-command arm was right the whole time while the top-level
`_arguments` kept its specs live past the command word, and the suite
stayed green. `tests/completions.sh` asks bash and fish for their
candidates and drives zsh through zpty, then asks the binary whether each
one is valid, in contexts including one where a flag is already typed. It
was checked against a copy of the script with the fault put back.

It found one on its first run: bash returned the shell list for
`completions` and `module` before looking at flags, so `envctl completions
-` offered nothing at all.
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Remove the envctl env version banner so output remains a pure KEY=VALUE stream.

Replace redaction booleans with --redact=WHEN, supporting never, auto, agent, tty, and always. Keep bare --redact as shorthand for --redact=always.

Add --show-control-chars for independent control-byte handling. Make --raw disable redaction and control-byte escaping. Support --raw with env.

Improve Bash, Fish, Zsh, and PowerShell completion generation with flag values and command-specific contexts. Add an integration harness that validates generated completions, invalid scopes, invalid values, already-typed flags, and --redact= value completion. Make the harness part of make test.

Improve Zsh completion tests with warm-up renders, rendered-output polling, timeouts, preserved stderr, and raw-byte diagnostics.

Update CI, documentation, the README, the manual, and test fixtures. Add coverage for redaction modes, invalid values, incompatible options, raw environment output, and control-byte display. Extend ShellCheck coverage to all test scripts. Add PowerShell formatting support and register the PSScriptAnalyzer dprint plugin.

Walkthrough

The CLI now supports selectable redaction modes, raw environment output, and independent control-character display. Environment output no longer includes a version header. Flag parsing accepts and validates --name=value syntax. Shell completions now include command-specific flags and permitted values. Documentation, manual pages, integration fixtures, and the test target reflect these changes. Dprint now includes PowerShell formatting and PSScriptAnalyzer support.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as envctl
  participant Policy as want_redact
  participant Environment as act_env_dump
  User->>CLI: Run env with redaction flags
  CLI->>Policy: Resolve RedactWhen
  Policy-->>CLI: Return masking decision
  CLI->>Environment: Pass redaction and sort settings
  Environment-->>User: Render environment entries
Loading

Possibly related PRs

  • kjanat/envctl#2: Updates environment-output and redaction code used by this change.
  • kjanat/envctl#5: Modifies redaction behaviour in src/main.c and src/redact.c.
  • kjanat/envctl#8: Modifies redaction behaviour in src/redact.c.

Poem

Redaction modes now obey the helm,
Raw output clears the realm.
Completions list each value bright,
Tests patrol each shell at night.
The CLI sails fixed and precise.

🚥 Pre-merge checks | ✅ 4 | ❌ 4

❌ Failed checks (4 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title describes the redaction and completion changes, but it is 75 characters long and does not start with a conventional lowercase verb prefix. Use a concise conventional title, such as add: support selectable redaction and test completions.
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 30.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Changelog Update ⚠️ Warning The PR changes eight files under src, but no CHANGELOG.md exists and no changelog path changed in the PR range. Add CHANGELOG.md with a ## [Unreleased] section and an Added/Changed/Fixed/Removed entry describing the source changes.
Semver Version Bump Validation ⚠️ Warning The PR modifies source files, but neither the base nor HEAD contains an eligible version file or a SemVer version bump; it also changes public C signatures. Add an eligible VERSION or build metadata field and apply the required SemVer bump. Use a MAJOR increment for the public signature changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the banner removal, redaction modes, raw output, completion testing, and verification steps.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Agents.Md Documentation Updated ✅ Passed No AGENTS.md file exists in the repository; therefore the documentation-update condition is not applicable.

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

@kjanat kjanat self-assigned this Aug 10, 2026
@kjanat kjanat added the cr:review Allow CodeRabbit review label Aug 10, 2026
The Makefile passes $(CURDIR)/$(BIN), which on Windows is a drive-letter
path like D:/a/envctl/envctl/envctl.exe. The absolute-path guard only
recognised a leading slash, so it prefixed $PWD again and the harness
reported the doubled path as not executable.

tests/run.sh has accepted both forms since it was written; its guard was
copied here without the drive-letter half.
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@kjanat kjanat changed the title Choose redaction with --redact=WHEN, and test completions by running them Choose redaction with --redact=WHEN, and test completions by running them Aug 10, 2026
kjanat added 2 commits August 10, 2026 15:06
Review findings on the branch.

`envctl env` masks unconditionally by default, but an explicit WHEN
replaces that default rather than layering on it, so `--redact=agent`
leaves the dump unmasked when no agent is present. The registry text and
both docs claimed only `--redact=never` could change it. They also said
`--redact=never` prints the environment exactly as it stands, which skips
that it still escapes control bytes on a terminal; only `--raw` drops
both.

The value spellings reached the per-command flag lists but not the arm
each generator uses before a command word, so `envctl --redact=` offered
nothing in bash, fish and pwsh while the parser accepts
`envctl --redact=never env`. Only zsh was right, because its no-command
arm already went through the shared spec writer.

The harness never probed after a `=`, which is the whole of the new
behaviour, and it never probed an alias. It does both now, and the values
come from the parser itself: a bogus value makes it name the valid ones.
Checked against a script with the spellings stripped, where all five
values fail in every context including the one before a command word.

`env-cmd-raw.case` used ordinary text, so it would have passed if `--raw`
lost its control-byte half. It carries an ESC byte now.

`shellcheck -x -o all` is clean across the suite. An unused alias map in
the new harness is gone because the aliases are now exercised, the
shell dispatches carry a default arm, and four pipelines in run.sh that
discarded the first command's status were split.
CodeRabbit flagged the skip on an unavailable shell. The CI logs show
what that was hiding: ubuntu-26.04 and windows-2025-vs2026 both printed
`fish (not installed)` and `zsh (not installed)` and then passed, while
macos-15 runs bash 3.2, where `mapfile` and `declare -A` do not exist,
so the script died before its first check and `make test` still exited
0. The bash arm was the only one that had ever run, and zsh is the shell
whose defect the harness was written to catch.

The workflow installs zsh and fish on Linux, and fish on macOS where zsh
is already present. It installs bash there too: Apple ships 3.2 for
licensing reasons and the harness needs 4 for `mapfile` and associative
arrays. A version check at the top now says so, rather than the script
failing four times in a row and returning success.

A missing shell is a failure rather than a skip. `COMPLETIONS_SHELLS`
narrows the set for Windows, which has no native fish and reaches zsh
only through MSYS2, and an empty value is rejected so the override
cannot quietly check nothing. The closing line names the shells it
drove, so a green run states its own coverage.

CONTRIBUTING.md gains a section on the harness, which it had never
mentioned, and its shellcheck line covers tests/*.sh instead of run.sh
alone.
coderabbitai[bot]

This comment was marked as resolved.

macos-15 reported 57 mismatches, every one of them zsh, while bash and
fish were clean and Windows was green. The driver wrote a TAB into the
zpty, slept a second, then read whatever had arrived. That is a race,
and a slower runner loses it. Dropping the sleep to 0.02s locally
reproduces the failure exactly: the same 57 lines in the same order.

The read now collects until the stream has been quiet for a second, with
a ten second ceiling. zsh loads `_arguments` and its helpers from disk on
the first completion in a fresh shell, which outruns that deadline on a
cold cache, so each shell gets one discarded warm-up render first.
Without it the first context failed and the rest passed, which is worse
than a consistent failure.

The harness also keeps its evidence now. stderr from the pty goes to a
file rather than /dev/null, so a failed zpty setup is visible, and a
mismatch dumps the raw bytes of the first failing context through `od -c`.
The first cut printed the last render, which had usually succeeded.

The zsh arm costs a second per render, taking `make test` from about 15s
to 1m14s.
@kjanat kjanat added enhancement New feature or request agent-detect Coding agent detection and TTY rules completions Shell completion scripts and the pwsh module tests Suite and harness build Makefile, CI, release artifacts portability Windows, macOS, BSD behavior documentation Improvements or additions to documentation labels Aug 10, 2026
The label set had no name for the four shell scripts and the pwsh
module, which is what this branch and issue #9 are mostly about.
@kjanat
kjanat merged commit 48658ca into master Aug 10, 2026
10 checks passed
@kjanat
kjanat deleted the feat/redact-when-modes branch August 10, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-detect Coding agent detection and TTY rules build Makefile, CI, release artifacts completions Shell completion scripts and the pwsh module cr:review Allow CodeRabbit review documentation Improvements or additions to documentation enhancement New feature or request portability Windows, macOS, BSD behavior tests Suite and harness

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant