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
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.ymlmay carry adefaultsblock 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.accountorgit.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.gitcarries an account and a token - and a repository whose machine config writes
account: ""andtoken: "" - When the configuration is loaded
- Then the effective
git.accountandgit.tokenare 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.gitcarries a token - and a repository whose machine config writes
token:with nothing behind it - When the configuration is loaded
- Then the effective
git.tokenis 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.gitcarries 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.baseUrlnames a Gitea - and a repository whose machine config sets
gitea.baseUrl: "" - When the configuration is loaded
- Then the effective
gitea.baseUrlis 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 initruns - Then the machine config contains the
gitblock commented out, with the detected account offered inside the comment- and loading that repository's configuration yields no
gitsection at all
- and loading that repository's configuration yields no
Verified by
- InitCommandTest "creates config files with auto-detected values"
- InitCommandTest "detects account from https url"
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.accountandgit.tokenexplicitly (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 inWerkatorConfig— 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.