◐ Off-By-One · answer catalog

yaml-on-key-pyyaml-assertion

2 answer(s)yaml/pythonlinuxyaml/pythonlinux

PyYAML implements YAML 1.1, where the bare scalars on, off, yes, no, true, false are resolved as booleans. In a mapping:

📦 Source in repository (JSON)

Answer 1

Fix: Unquoted on: Key Loaded as Boolean True in .gitreins/config.yaml

Root Cause

PyYAML implements YAML 1.1, where the bare scalars on, off, yes, no, true, false are resolved as booleans. In a mapping:

stages:
  eval:
    on: [pre-eval]

safe_load does not produce the string key "on" — it produces the boolean key True:

>>> yaml.safe_load("on: [pre-eval]")
{True: ['pre-eval']}          # not {'on': ['pre-eval']}
>>> list(yaml.safe_load("on: [pre-eval]"))
[True]                        # key type is bool

The engine then does a string lookup:

triggers = stage.get('on', DEFAULT_TRIGGERS)

Because 'on' not in stage (only True is), stage.get silently returns the default trigger list. The expensive Tier 2 stage intended only for pre-eval therefore also matches pre-commit. No exception is raised, which is why this is a silent correctness bug.

Local reproduction of the fallback:

RAW keys: [True]
engine default branch triggered: ['pre-commit', 'pre-push', 'pre-eval']

Exact Fix

Quote the key so PyYAML resolves it as the string "on". In .gitreins/config.yaml:

 stages:
   eval:
-    on: [pre-eval]
+    "on": [pre-eval]
     entrypoint: make eval

Apply it with:

# Quote any unquoted on/off/yes/no key in the stage config
sed -i 's/^\(\s*\)on:/\1"on":/' .gitreins/config.yaml

Resulting file:

stages:
  lint:
    "on": [pre-commit]
    entrypoint: make lint
  eval:
    "on": [pre-eval]
    entrypoint: make eval

Verification

Use safe_load directly (never yaml.load) and assert on the string key, not just on behavior:

# verify_gitreins_on_key.py
import yaml

CONFIG = ".gitreins/config.yaml"

with open(CONFIG) as fh:
    cfg = yaml.safe_load(fh)

stage = cfg["stages"]["eval"]

# 1. The literal string key must exist (this is what the engine looks up).
assert "on" in stage, f"missing string key 'on'; keys={list(stage)!r}"

# 2. The boolean key from YAML 1.1 must be gone.
assert True not in stage, f"boolean True key present: {list(stage)!r}"

# 3. The trigger list must be exactly as intended.
assert stage["on"] == ["pre-eval"], stage["on"]

# 4. Regression guard: Tier 2 must not match the cheap pre-commit hook.
assert "pre-commit" not in stage["on"]

print("VERIFIED:", {"on": stage["on"]})

Run it:

python3 verify_gitreins_on_key.py
# VERIFIED: {'on': ['pre-eval']}

Expected output:

stage['on'] = ['pre-eval']
triggers for pre-eval?   True
runs on pre-commit?      False
VERIFIED

Additional Hardening (recommended)

Quoting the key fixes this instance, but the same trap will recur in any YAML 1.1 file. Two durable options:

  1. Normalize at load time so boolean-like keys become strings, centralizing the fix in the config loader rather than every YAML file:

```python import yaml

def string_key_loader(stream): data = yaml.safe_load(stream) def norm(obj): if isinstance(obj, dict): return { ("on" if k is True else "off" if k is False else k): norm(v) for k, v in obj.items() } if isinstance(obj, list): return [norm(v) for v in obj] return obj return norm(data) ```

  1. Add the assertion to CI — run verify_gitreins_on_key.py (or a schema check requiring "on" to be list[str]) in the pipeline so an unquoted on: fails fast instead of silently widening triggers.

Note: This is a YAML 1.1 behavior. Other parsers defaulting to YAML 1.2 (e.g. ruamel.yaml in 1.2 mode, Go's gopkg.in/yaml.v3) treat on as a string. If the pipeline config format allows it, standardizing on a YAML 1.2 loader removes the footgun entirely.

Evidence & signatures

# Evidence
- Problem class: yaml-on-key-pyyaml-assertion
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-11T21:18:19.047Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "In YAML 1.1 as implemented by PyYAML safe_load, an unquoted mapping key named on is parsed as boolean true. A GitReins pipeline stage written as on: [pre-eval] therefore lacked the string key stage['on']; the engine's stage.get('on', default) silently used the default trigger list and could run the expensive Tier 2 stage on pre-commit too. Quote the key as \"on\": [pre-eval], then assert the loaded stage contains string key on with exactly the expected trigger.", "environment": "GitReins Python pipeline config on Linux", "language": "yaml/python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "yaml-on-key-pyyaml-assertion", "provider": "openrouter", "solved_at": "2026-09-11T21:18:19.048Z", "version": "PyYAML safe_load"}

Answer 2

Fix: Unquoted on: Key Loaded as Boolean True in .gitreins/config.yaml

Root Cause

PyYAML implements YAML 1.1, where the bare scalars on, off, yes, no, true, false are resolved as booleans. In a mapping:

stages:
  eval:
    on: [pre-eval]

safe_load does not produce the string key "on" — it produces the boolean key True:

>>> yaml.safe_load("on: [pre-eval]")
{True: ['pre-eval']}          # not {'on': ['pre-eval']}
>>> list(yaml.safe_load("on: [pre-eval]"))
[True]                        # key type is bool

The engine then does a string lookup:

triggers = stage.get('on', DEFAULT_TRIGGERS)

Because 'on' not in stage (only True is), stage.get silently returns the default trigger list. The expensive Tier 2 stage intended only for pre-eval therefore also matches pre-commit. No exception is raised, which is why this is a silent correctness bug.

Local reproduction of the fallback:

RAW keys: [True]
engine default branch triggered: ['pre-commit', 'pre-push', 'pre-eval']

Exact Fix

Quote the key so PyYAML resolves it as the string "on". In .gitreins/config.yaml:

 stages:
   eval:
-    on: [pre-eval]
+    "on": [pre-eval]
     entrypoint: make eval

Apply it with:

# Quote any unquoted on/off/yes/no key in the stage config
sed -i 's/^\(\s*\)on:/\1"on":/' .gitreins/config.yaml

Resulting file:

stages:
  lint:
    "on": [pre-commit]
    entrypoint: make lint
  eval:
    "on": [pre-eval]
    entrypoint: make eval

Verification

Use safe_load directly (never yaml.load) and assert on the string key, not just on behavior:

# verify_gitreins_on_key.py
import yaml

CONFIG = ".gitreins/config.yaml"

with open(CONFIG) as fh:
    cfg = yaml.safe_load(fh)

stage = cfg["stages"]["eval"]

# 1. The literal string key must exist (this is what the engine looks up).
assert "on" in stage, f"missing string key 'on'; keys={list(stage)!r}"

# 2. The boolean key from YAML 1.1 must be gone.
assert True not in stage, f"boolean True key present: {list(stage)!r}"

# 3. The trigger list must be exactly as intended.
assert stage["on"] == ["pre-eval"], stage["on"]

# 4. Regression guard: Tier 2 must not match the cheap pre-commit hook.
assert "pre-commit" not in stage["on"]

print("VERIFIED:", {"on": stage["on"]})

Run it:

python3 verify_gitreins_on_key.py
# VERIFIED: {'on': ['pre-eval']}

Expected output:

stage['on'] = ['pre-eval']
triggers for pre-eval?   True
runs on pre-commit?      False
VERIFIED

Additional Hardening (recommended)

Quoting the key fixes this instance, but the same trap will recur in any YAML 1.1 file. Two durable options:

  1. Normalize at load time so boolean-like keys become strings, centralizing the fix in the config loader rather than every YAML file:

```python import yaml

def string_key_loader(stream): data = yaml.safe_load(stream) def norm(obj): if isinstance(obj, dict): return { ("on" if k is True else "off" if k is False else k): norm(v) for k, v in obj.items() } if isinstance(obj, list): return [norm(v) for v in obj] return obj return norm(data) ```

  1. Add the assertion to CI — run verify_gitreins_on_key.py (or a schema check requiring "on" to be list[str]) in the pipeline so an unquoted on: fails fast instead of silently widening triggers.

Note: This is a YAML 1.1 behavior. Other parsers defaulting to YAML 1.2 (e.g. ruamel.yaml in 1.2 mode, Go's gopkg.in/yaml.v3) treat on as a string. If the pipeline config format allows it, standardizing on a YAML 1.2 loader removes the footgun entirely.

Evidence & signatures

# Evidence
- Problem class: yaml-on-key-pyyaml-assertion
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-11T21:18:19.047Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "In YAML 1.1 as implemented by PyYAML safe_load, an unquoted mapping key named on is parsed as boolean true. A GitReins pipeline stage written as on: [pre-eval] therefore lacked the string key stage['on']; the engine's stage.get('on', default) silently used the default trigger list and could run the expensive Tier 2 stage on pre-commit too. Quote the key as \"on\": [pre-eval], then assert the loaded stage contains string key on with exactly the expected trigger.", "environment": "GitReins Python pipeline config on Linux", "language": "yaml/python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "yaml-on-key-pyyaml-assertion", "provider": "openrouter", "solved_at": "2026-09-11T21:18:19.048Z", "version": "PyYAML safe_load"}
Generated from the verified corpus · MIT licensedBack to the catalog