> **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: ```yaml 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 - [ConfigLoaderTest "a blank credential in a repository layer does not shadow the instance defaults"](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderTest.kt) #### 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 - [ConfigLoaderTest "a credential written without a value is as blank as an empty string"](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderTest.kt) #### 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 - [ConfigLoaderTest "a credential that has a value still wins over the instance defaults"](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderTest.kt) #### 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 - [ConfigLoaderTest "a blank value that is not a credential still overrides"](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderTest.kt) #### 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 - [ConfigLoaderTest "without a home config a blank credential is left alone"](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderTest.kt) ### 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 - [InitCommandTest "creates config files with auto-detected values"](../../src/test/kotlin/de/hoennig/werkator/commands/InitCommandTest.kt) - [InitCommandTest "detects account from https url"](../../src/test/kotlin/de/hoennig/werkator/commands/InitCommandTest.kt) #### 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 - [ConfigLoaderTest "a config file of nothing but comments is a layer that sets nothing"](../../src/test/kotlin/de/hoennig/werkator/config/ConfigLoaderTest.kt) ## 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.