badcodebadcode is a release-blocking scan for text that must never reach a
release under any circumstances -- names, internal codenames,
credentials-shaped tokens, "DO NOT SHIP" markers. relcore (see
repoman-070-releases.md) runs it automatically and unconditionally as
the very first thing it does, on every release, including on --resume
-- not a step that can be skipped or configured away.
The pattern list is deliberately never stored in any repository. It lives in a local, per-machine config directory instead, because a blocklist that ships inside the repo it's protecting can be edited by whoever has commit access -- including an overly-permissive or compromised agent -- in the same change that would have been caught by it. Keeping it local and out-of-band is the entire point: the check exists outside the reach of "just edit the config to stop being blocked."
This has a direct consequence worth stating plainly: a fresh session
in a fresh sandbox has no badcode config until one is recreated.
Nothing is wrong if badcode check reports zero patterns configured --
that's the expected starting state, not a broken installation. See
"No config configured" below for what that actually looks like and why
it's a soft pass, not a silent one.
The OS-appropriate user config directory (via os.UserConfigDir() --
~/.config on Linux, ~/Library/Application Support on macOS,
%AppData% on Windows) under a repoman subdirectory, overridable via
the REPOMAN_BADCODE_DIR environment variable. Two file formats are
read, both optional, both additive if both exist:
badcode.txt one pattern per line; blank lines and lines starting
with '#' are ignored.
badcode.json [{"pattern": "...", "reason": "..."}, ...] -- "reason"
is optional, and included in the refusal message when
a pattern matches, so a match is actionable rather
than just a bare string.
Case-insensitive, literal substring search. Not regex. Regex adds failure modes (a pattern that silently doesn't compile, ReDoS, an off-by-one in an anchor) to a check whose entire value is that it never has a false "clean" result -- a plain substring search can't fail in a way that looks like success.
This does mean a pattern split across an ordinary line wrap is checked
for too, not just a pattern sitting entirely on one line -- an
accidental line wrap is enough to split a forbidden name across two
lines by complete accident, not just deliberate evasion, and a match
found only that way is reported with a [spans lines N-M] marker so
the message stays precise about what actually happened.
Binary files are skipped (the same NUL-byte heuristic git itself uses), so a forbidden byte sequence that happens to occur coincidentally inside a compiled artifact is never flagged.
$ repoman badcode check .
BADCODE CHECK OK (4 pattern(s) checked)
A real match refuses clearly, with file, line, provenance status, and (if configured) the reason:
$ repoman badcode check .
BADCODE CHECK FAIL: 1 match(es)
ERROR badcode-match: pattern "INTERNAL_CODENAME" (project codename --
must never appear in anything meant for external publication) found
in README.md:12 [no provenance record]: still using INTERNAL_CODENAME
in this draft
The bracketed provenance status (T-04) is one of three labels:
repoman (the matched file's current content matches repoman's own
journal record -- it genuinely was written through ed/strreplace),
no provenance record (the file has never been touched by repoman at
all), or stale provenance record (repoman wrote the file at some
point, but it has since changed outside repoman's own write paths
too). No adjacent secret-scanning tool (Gitleaks, TruffleHog, and
similar) can make this distinction, since none of them own the
editing layer the way repoman does.
$ repoman badcode check .
WARN no badcode patterns configured in /home/user/.config/repoman -- nothing checked
BADCODE CHECK OK (0 patterns configured)
Exit 0 -- optional, local, per-operator tooling shouldn't block every
release for an operator who hasn't set it up yet. But the WARN line
is always there, on every invocation with no config, so a check that
never actually ran can't be mistaken for a check that ran clean. This
is the same guard principle repoman-020-failure-modes.md describes
for dormant guards generally: a check that never actually ran proves
nothing, and this one says so plainly rather than looking identical to
a real pass.
Because the config is deliberately never committed, a fresh session starts with none. If the project has one configured (check prior session notes, or ask), recreate it at the start of any session doing release-related work, before that work begins -- not discovered as missing partway through a release:
mkdir -p ~/.config/repoman
cat > ~/.config/repoman/badcode.json << 'EOF'
[
{"pattern": "...", "reason": "..."}
]
EOF
relcorerelcore <version> runs badcode check against the whole tree as the
first thing it does, before reading release.steps, before any step
runs, including on --resume -- it is not part of the resumable-steps
journal at all, so there is nothing for --resume to bypass. A real
match blocks the entire release; fixing the match (removing the
matched content) and re-running proceeds normally.
Immediately alongside it, relcore also runs a provenance pre-flight
(T-03, see repoman-088-provenance.md) with the exact same discipline
-- unconditional, not a release.steps entry, not resumable, no
bypass on --resume. An unsanctioned out-of-band edit to a
journal-tracked file blocks the release the same way a badcode hit
does; repoman provenance sanction FILE --reason "..." is the only
way through. See repoman-070-releases.md for the full release
workflow this sits inside.