Skip to content

Commit 367976f

Browse files
fix(ci): repair workflows that are not valid YAML and have never run (#76)
**These workflow files are not valid YAML, so they have never run.** Not "ran and failed" — never ran. GitHub Actions rejects the file before creating any job: the run is recorded as `failure` with **no jobs, no log and no check run**, and `gh pr checks` shows no row at all. A red mark with nothing behind it to read. ## Cause A sweep added permission declarations **by line position rather than by parsing the document**. Three invalid shapes resulted: **A — a mapping indented under a scalar value** ```yaml permissions: read-all actions: read # read-all is a SCALAR; it cannot take children ``` `read-all` already grants everything `actions: read` would, so the orphaned line is dropped and nothing is lost. **B — injected inside another block** ```yaml on: permissions: contents: read # two colons, and illegal under `on:` anyway push: ``` **C — a literal `\n` that was never interpreted**, gluing the escape's `n` to the key: ```yaml runs-on: ubuntu-latest npermissions: # "\npermissions:" written literally ``` Only a text-level writer emitting an uninterpreted escape can produce that. ## Verified, not assumed Every workflow in this repository parses after the change. The repairer **refuses to write any file that does not parse and still contain jobs** afterwards. Where a job-level `permissions:` line was removed, a **read-only top-level `permissions:` remains**, so nothing is widened — and if none would remain, the tool reports that rather than inventing one. Guessing a permission set is how you silently over-grant. ## Estate context **67 repositories and 100 workflow files are in this state.** The most frequently broken file is **`workflow-linter.yml`, in 22 repositories** — followed by `scorecard.yml` (20) and `dogfood-gate.yml` (13). The workflow whose job is to lint workflows was itself unparseable, so **it never ran, and never caught this or anything else.** The check that would have found the damage was destroyed by the same sweep that caused it. ## So it cannot recur invisibly Detection is being added upstream: a strict-YAML check in the governance reusable — hyperpolymath/standards#582. Ordinary validation cannot see this class of fault, because `yaml.safe_load` silently accepts duplicate keys and only a full parse catches the malformed indentation. ## Expect this repository to get louder Workflows that have been failing silently will now actually run, and some will find real problems that have been invisible for as long as the files have been broken. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 27cf5e5 commit 367976f

2 files changed

Lines changed: 20 additions & 21 deletions

File tree

‎.github/workflows/dogfood-gate.yml‎

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -264,26 +264,26 @@ jobs:
264264
265265
# Validate TOML structure using Python 3.11+ tomllib
266266
python3 -c "
267-
import tomllib, sys
268-
with open('eclexiaiser.toml', 'rb') as f:
269-
data = tomllib.load(f)
270-
project = data.get('project', {})
271-
if not project.get('name', '').strip():
272-
print('ERROR: project.name is required', file=sys.stderr)
273-
sys.exit(1)
274-
functions = data.get('functions', [])
275-
if not functions:
276-
print('ERROR: at least one [[functions]] entry is required', file=sys.stderr)
277-
sys.exit(1)
278-
for fn in functions:
279-
if not fn.get('name', '').strip():
280-
print('ERROR: function name cannot be empty', file=sys.stderr)
281-
sys.exit(1)
282-
if not fn.get('source', '').strip():
283-
print(f'ERROR: function {fn[\"name\"]} has no source path', file=sys.stderr)
284-
sys.exit(1)
285-
print(f'Valid: {project[\"name\"]} ({len(functions)} function(s))')
286-
" || {
267+
import tomllib, sys
268+
with open('eclexiaiser.toml', 'rb') as f:
269+
data = tomllib.load(f)
270+
project = data.get('project', {})
271+
if not project.get('name', '').strip():
272+
print('ERROR: project.name is required', file=sys.stderr)
273+
sys.exit(1)
274+
functions = data.get('functions', [])
275+
if not functions:
276+
print('ERROR: at least one [[functions]] entry is required', file=sys.stderr)
277+
sys.exit(1)
278+
for fn in functions:
279+
if not fn.get('name', '').strip():
280+
print('ERROR: function name cannot be empty', file=sys.stderr)
281+
sys.exit(1)
282+
if not fn.get('source', '').strip():
283+
print(f'ERROR: function {fn[\"name\"]} has no source path', file=sys.stderr)
284+
sys.exit(1)
285+
print(f'Valid: {project[\"name\"]} ({len(functions)} function(s))')
286+
" || {
287287
echo "::error file=eclexiaiser.toml::Invalid eclexiaiser.toml — see step output for details"
288288
exit 1
289289
}

‎.github/workflows/scorecard.yml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ on:
99
branches: [main]
1010

1111
permissions: read-all
12-
actions: read
1312

1413
jobs:
1514
analysis:

0 commit comments

Comments
 (0)