Files
werkator/docs/prs/2026-09-11-PR#51-blank-credentials-do-not-shadow.md
1f14f71fb3
werkator build succeeded after 01:28
config: a blank credential must not disable an inherited one (#51)
Five of six repositories on the production instance reported no commit
status to Gitea, silently. The cause was a placeholder: init wrote every
machine config with account: "" and token: "", the machine layer always
wins, and those empty values overrode the instance-wide defaults.git.
GiteaClient found no token and stayed quiet, because publishing a status
is non-fatal by design. The one repository that worked was the one whose
machine config happened to have no git block.

It reproduced itself, too: repo-add runs init, so every repository added
to an instance arrived with its inherited credentials already disabled.

Fixed at both ends. ConfigLoader drops a blank git.account/git.token from
a repository layer instead of letting it shadow the defaults below, and
names the file once — a credential is the one value that cannot mean
anything when empty, while an empty setting can be a choice, which
Scenario#51.04 pins down. That is also what repairs a running
installation: the next start inherits correctly with no file edited.

InitCommand writes the git block commented out, with the detected account
offered inside the comment. A template that asserts token: "" makes a
claim it has no basis for. loadFile then had to learn that a file of
nothing but comments is a layer that sets nothing — readValue answers
such a file with "No content to map" — which parseYaml has always done
for the branch layer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #51
2026-09-11 06:28:37 +02:00

8.2 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

On the production instance, five of six repositories reported no commit status to Gitea at all. Nothing had failed, nothing was logged, and the builds themselves were green.

The cause is a placeholder. werkator init wrote every repository's machine config with the credentials spelled out but empty:

git:
  account: ""
  token: ""

The machine layer always wins, so those empty values overrode the instance-wide defaults.git of ~/.werkator.yml (ADR 0009). GiteaClient.isEnabled needs a non-blank git.token, found none, and stayed quiet — publishing a status is non-fatal by design. The one repository that worked was the one whose machine config happened to have no git block.

The defect reproduces itself: tools/remote werkator repo-add runs init, so every repository added to an instance arrives with its inherited credentials already disabled. Adding werkprobe is how it was found.

Non-Goals

  • No change to what a setting does when written empty — only credentials are treated as placeholders.
  • No new configuration key, and no change to where credentials may be written.
  • Repairing the machine configs of a running installation is deployment work, not code; this PR makes the next start repair itself.

The Scenarios

Feature: an unfilled credential does not disable an inherited one

Background

  • The instance config ~/.werkator.yml may carry a defaults block that sits below every repository layer (ADR 0009).
  • A repository's own layers — project config, applied fragment, machine config — win over it, in that order.
  • A credential is git.account or git.token.

Scenario#51.01: A blank credential falls through to the instance defaults

So that a repository added to an instance keeps the credentials it was meant to inherit.

  • Given an instance config whose defaults.git carries an account and a token
  • and a repository whose machine config writes account: "" and token: ""
  • When the configuration is loaded
  • Then the effective git.account and git.token are the instance's
    • and the file is named once in a warning, because it still wants fixing
Verified by

Scenario#51.02: A key written without a value counts as blank

So that token: and token: "" mean the same thing, since both are what an unfilled template looks like.

  • Given an instance config whose defaults.git carries a token
  • and a repository whose machine config writes token: with nothing behind it
  • When the configuration is loaded
  • Then the effective git.token is the instance's
Verified by

Scenario#51.03: A credential that has a value still wins

So that a repository authenticating as someone else on this machine keeps doing so.

  • Given an instance config whose defaults.git carries an account and a token
  • and a repository whose machine config sets an account but leaves the token blank
  • When the configuration is loaded
  • Then the account is the repository's and the token is the instance's — the rule is per key
Verified by

Scenario#51.04: A blank setting is still a setting

So that "report nowhere" stays sayable.

  • Given an instance config whose defaults.gitea.baseUrl names a Gitea
  • and a repository whose machine config sets gitea.baseUrl: ""
  • When the configuration is loaded
  • Then the effective gitea.baseUrl is empty — only credentials are treated as placeholders
Verified by

Scenario#51.05: Without an instance config nothing changes

So that a single-repository installation behaves exactly as before.

  • Given no instance config
  • and a repository whose machine config writes token: ""
  • When the configuration is loaded
  • Then the effective token is empty, and nothing is warned about — there is nothing below to shadow
Verified by

Feature: the template stops asserting what it does not know

Scenario#51.06: init writes the credentials commented out

So that a fresh repository inherits the instance credentials instead of disabling them.

  • Given a repository with an https origin
  • When werkator init runs
  • Then the machine config contains the git block commented out, with the detected account offered inside the comment
    • and loading that repository's configuration yields no git section at all
Verified by

Scenario#51.07: A config file of nothing but comments is a layer that sets nothing

So that the file init now writes can be read at all, and an operator may comment every key out again.

  • Given a machine config holding only comments
  • When the configuration is loaded
  • Then the other layers apply unchanged, and no error is raised
Verified by

The Solution

Two changes, at the two ends of the defect.

ConfigLoader.withoutBlankCredentials drops a blank git.account/git.token from each repository layer before merging, so it cannot shadow the defaults.git below it, and logs once per file — scoped like checkVersion, so the message names the file to fix. Dropped rather than honoured because a credential is the one kind of value that cannot mean anything when empty: there is no build that wants to authenticate as nobody. Everything else keeps the existing semantics, deliberately — an empty setting can be a choice, and Scenario#51.04 pins that down. This is also what repairs a running installation: the next start inherits correctly without anyone editing a file.

InitCommand no longer writes the git block out with empty values; it writes it commented out, with the detected account offered inside the comment and a line saying why. A template that asserts token: "" is a template that makes a claim it has no basis for.

ConfigLoader.loadFile then had to learn that a file of nothing but comments is a layer that sets nothing: readValue answers such a file with "No content to map due to end-of-input", so it now goes through readTree and treats a missing node as an empty layer — the same thing parseYaml has always done for the branch layer.

Open Questions

  • The rule names git.account and git.token explicitly (CREDENTIAL_KEYS) rather than deriving "is a secret" from the schema. A third credential would have to be added there by hand; the alternative — marking secrets in WerkatorConfig — is more machinery than two keys justify today.

Additional Changes

  • docs/configuration.md: the layering section now states the credential rule, and the machine-config section says that the block is written commented out and that a comment-only file is a valid layer.
  • .claude/skills/architecture/SKILL.md: the configuration section names the rule.

Follow-up PRs

  • None. The machine configs of the running instance were repaired by hand when the defect was found; after this PR they would have repaired themselves.