PyYAML implements YAML 1.1, where the bare scalars on, off, yes, no, true, false are resolved as booleans. In a mapping:
on: Key Loaded as Boolean True in .gitreins/config.yamlPyYAML 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']
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
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
Quoting the key fixes this instance, but the same trap will recur in any YAML 1.1 file. Two durable options:
```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) ```
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.yamlin 1.2 mode, Go'sgopkg.in/yaml.v3) treatonas a string. If the pipeline config format allows it, standardizing on a YAML 1.2 loader removes the footgun entirely.
# 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"}on: Key Loaded as Boolean True in .gitreins/config.yamlPyYAML 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']
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
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
Quoting the key fixes this instance, but the same trap will recur in any YAML 1.1 file. Two durable options:
```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) ```
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.yamlin 1.2 mode, Go'sgopkg.in/yaml.v3) treatonas a string. If the pipeline config format allows it, standardizing on a YAML 1.2 loader removes the footgun entirely.
# 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"}