Skip to content

fix(config): quoted YAML booleans silently coerced to true across 9 config loaders - #558

Open
Tet-9 wants to merge 2 commits into
vouchdev:testfrom
Tet-9:fix/compile-two-phase-quoted-false-yaml
Open

fix(config): quoted YAML booleans silently coerced to true across 9 config loaders#558
Tet-9 wants to merge 2 commits into
vouchdev:testfrom
Tet-9:fix/compile-two-phase-quoted-false-yaml

Conversation

@Tet-9

@Tet-9 Tet-9 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

what

bool(x) on an arbitrary config value is a trap: yaml.safe_load resolves an unquoted true/false to a real Python bool, but a mistakenly-quoted "false" in config.yaml stays a non-empty string — and bool("false") is True. This exact bug was independently repeated across the codebase in 9 places:

  • compile.py: two_phase
  • session_split.py, admission.py, inbox.py, recall.py, capture.py, volunteer_context.py: enabled
  • admission.py: reject_uncited_session_pages (found alongside the enabled fix, same function)
  • proposals.py: auto_approve_on_receipt, read via _review_config() at 4 call sites — 2 of which didn't even wrap it in bool(), just used the raw config value in a truthy if

That last one is the one that actually matters: auto_approve_on_receipt gates whether a claim can be durably approved with no human reviewer. A mistakenly-quoted "false" in config.yaml was silently read as enabled by every caller, not just the site this PR happened to start from.

why

Rather than patch each of the 9 read sites individually with a repeated inline fix, this adds one shared, tested primitive (coerce_bool) and — for the security-relevant case — normalizes at the single choke point (_review_config()) all 4 callers already funnel through, so the fix can't drift out of sync the way 9 independent bool() calls already had.

coerce_bool() is deliberately fail-soft (bad/unrecognized value degrades to the caller's default), matching the posture compile.py's own _coerce() already documents for its numeric fields. This is a different, and correct, choice from install_adapter.py's install.yaml flags, which raise hard — appropriate for a one-time CLI install, wrong for config read on every session/render.

Not included: openclaw/types.py's CompactParams.force uses the same bool(raw.get(...)) shape, but it parses a JSON wire payload from an external host (OpenClaw), not YAML config. JSON has native booleans, so the quoted-string trap doesn't apply the same way — different root cause, left out of this sweep.

invariants held

  • Fail-soft posture preserved everywhere except proposals.py's security-relevant field, which now has one authoritative parse instead of 4 independent (and inconsistent) ones.
  • Legitimate quoted "true" still works correctly (tested explicitly, not just the negative case).
  • Every existing config test still passes unchanged; new tests are additive only.

tests

  • New tests/test_config_coerce.py: unit tests for the coercer itself (recognized spellings, case-insensitivity, whitespace, fallback behavior).
  • A quoted-"false"-does-not-enable regression test added to every affected module's existing test file (test_compile.py, test_session_split.py, test_admission.py, test_inbox.py, test_recall.py, test_capture.py, test_volunteer_context.py).
  • test_receipt_auto_approve.py: two regression tests through the real approval path (approve() + auto_approve_receipts()) proving self-approval stays blocked on a quoted-"false" gate, plus one proving quoted-"true" still works.
  • Full local suite: 1799 passed (5 pre-existing failures in test_cli.py/test_delete.py/test_hot_memory.py, unrelated — verified no reference to any file this PR touches).

Summary by CodeRabbit

  • Bug Fixes

    • Improved YAML boolean parsing consistency across admission, capture, compilation, inbox, recall, session splitting, volunteer context, and receipt auto-approval.
    • Quoted values like "false"/"true" are now interpreted deterministically, preventing unintended feature enablement.
    • Added normalization for common true/false spellings and whitespace, with unknown inputs safely falling back to configured defaults.
  • Tests

    • Added regression tests covering quoted boolean strings for each affected setting plus full coverage for the new boolean coercion helper.

@Tet-9
Tet-9 requested a review from plind-junior as a code owner July 25, 2026 09:40
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 32060569-51bf-460d-b8d5-f18f688a99da

📥 Commits

Reviewing files that changed from the base of the PR and between 8b80900 and 52358dc.

📒 Files selected for processing (18)
  • src/vouch/admission.py
  • src/vouch/capture.py
  • src/vouch/compile.py
  • src/vouch/config_coerce.py
  • src/vouch/inbox.py
  • src/vouch/proposals.py
  • src/vouch/recall.py
  • src/vouch/session_split.py
  • src/vouch/volunteer_context.py
  • tests/test_admission.py
  • tests/test_capture.py
  • tests/test_compile.py
  • tests/test_config_coerce.py
  • tests/test_inbox.py
  • tests/test_recall.py
  • tests/test_receipt_auto_approve.py
  • tests/test_session_split.py
  • tests/test_volunteer_context.py
🚧 Files skipped from review as they are similar to previous changes (17)
  • src/vouch/config_coerce.py
  • tests/test_volunteer_context.py
  • src/vouch/inbox.py
  • src/vouch/session_split.py
  • src/vouch/recall.py
  • tests/test_config_coerce.py
  • src/vouch/admission.py
  • tests/test_capture.py
  • tests/test_compile.py
  • src/vouch/capture.py
  • tests/test_admission.py
  • tests/test_recall.py
  • src/vouch/compile.py
  • tests/test_inbox.py
  • tests/test_session_split.py
  • src/vouch/proposals.py
  • tests/test_receipt_auto_approve.py

Walkthrough

Adds shared YAML boolean coercion and applies it to feature configuration and receipt auto-approval settings. Regression tests cover quoted boolean strings, recognized spellings, whitespace handling, and fallback behavior.

Changes

Configuration boolean coercion

Layer / File(s) Summary
Boolean coercion helper and unit coverage
src/vouch/config_coerce.py, tests/test_config_coerce.py
Recognized boolean strings are normalized case-insensitively, real booleans pass through, and invalid values use the supplied default.
Feature configuration parsing
src/vouch/admission.py, src/vouch/capture.py, src/vouch/compile.py, src/vouch/inbox.py, src/vouch/recall.py, src/vouch/session_split.py, src/vouch/volunteer_context.py, tests/test_admission.py, tests/test_capture.py, tests/test_compile.py, tests/test_inbox.py, tests/test_recall.py, tests/test_session_split.py, tests/test_volunteer_context.py
Feature loaders use coerce_bool instead of direct truthiness conversion, so quoted "false" values remain disabled.
Receipt auto-approval configuration
src/vouch/proposals.py, tests/test_receipt_auto_approve.py
review.auto_approve_on_receipt is normalized to a boolean, with tests covering disabled and enabled receipt auto-approval behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • vouchdev/vouch#303: Added configuration namespaces whose boolean loading is updated here.
  • vouchdev/vouch#486: Added receipt-gated auto-approval logic consuming the normalized review setting.
  • vouchdev/vouch#522: Updated receipt auto-approval behavior using the same configuration gate.

Suggested reviewers: plind-junior

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the core change: fixing quoted YAML booleans across config loaders.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added storage kb storage, migrations, schemas, and proposals retrieval context, search, synthesis, and evaluation tests tests and fixtures size: M 200-499 changed non-doc lines labels Jul 25, 2026
@Tet-9
Tet-9 force-pushed the fix/compile-two-phase-quoted-false-yaml branch from 47f8083 to 8b80900 Compare July 25, 2026 09:43
@Tet-9

Tet-9 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior review if you may

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/vouch/config_coerce.py`:
- Line 1: Lowercase the prose sentence starts in the affected docstrings:
`Shared` in `src/vouch/config_coerce.py` lines 1-1, `Parse` in lines 29-30, and
`The` and `Callers` in `src/vouch/proposals.py` lines 480-489; preserve the
remaining wording and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0313d580-7924-4d1e-881d-07141d57e47d

📥 Commits

Reviewing files that changed from the base of the PR and between 93c4724 and 47f8083.

📒 Files selected for processing (18)
  • src/vouch/admission.py
  • src/vouch/capture.py
  • src/vouch/compile.py
  • src/vouch/config_coerce.py
  • src/vouch/inbox.py
  • src/vouch/proposals.py
  • src/vouch/recall.py
  • src/vouch/session_split.py
  • src/vouch/volunteer_context.py
  • tests/test_admission.py
  • tests/test_capture.py
  • tests/test_compile.py
  • tests/test_config_coerce.py
  • tests/test_inbox.py
  • tests/test_recall.py
  • tests/test_receipt_auto_approve.py
  • tests/test_session_split.py
  • tests/test_volunteer_context.py

Comment thread src/vouch/config_coerce.py Outdated
@Tet-9

Tet-9 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

publish coderabbit-approved status gate is failing on this PR — repo-side, not code-side

The gate workflow's last step fails with:
gh: Resource not accessible by integration (HTTP 403)
{"message":"Resource not accessible by integration",...,"status":"403"}

This is the default GITHUB_TOKEN behavior on fork-originated pull_request runs: for security, GitHub issues a read-only token to workflows triggered by pull_request from a fork, so any step that tries to write a commit status (as this gate does after evaluating CodeRabbit's verdict) gets a 403 regardless of what that verdict actually was. The preceding evaluate coderabbit verdict step passes fine — it's specifically the write-back step that's blocked.

This looks like the same root cause as #511 (also a fork-PR / GITHUB_TOKEN permissions gap in a workflow). A few standard fixes:

  • Add permissions: statuses: write to this job in the workflow YAML, or
  • Switch the trigger to pull_request_target for this job (with the usual caution that requires — it runs with base-branch permissions against a PR's head, so should only apply to the gate-publishing step, not anything that checks out/executes PR code), or
  • Use a PAT/GitHub App installation token instead of the default GITHUB_TOKEN for this specific step

Flagging since it'll block the merge gate on every fork PR, not just this one, until the workflow itself is fixed.

@Tet-9

Tet-9 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

@plind-junior , the remaining to do is all yours to take a look at

Tet-9 added 2 commits July 27, 2026 12:13
…onfig loaders

bool(x) on an arbitrary config value is a trap: yaml.safe_load resolves an
unquoted true/false to a real bool, but a mistakenly-quoted "false" stays
a non-empty string, and bool("false") is True. This exact bug was
independently repeated in 9 places:

- compile.py: two_phase
- session_split.py, admission.py, inbox.py, recall.py, capture.py,
  volunteer_context.py: enabled
- admission.py: reject_uncited_session_pages (found alongside the enabled
  fix, same function)
- proposals.py: auto_approve_on_receipt, read via _review_config() at 4
  call sites (2 of which used the bug directly with no bool() at all --
  a raw truthy check on the config dict value)

The last one is the one that matters most: auto_approve_on_receipt gates
whether a claim can be durably approved without a human reviewer. A
mistakenly-quoted "false" in config.yaml was silently read as enabled by
every caller, not just the one this PR happened to start from.

- New shared src/vouch/config_coerce.py: coerce_bool(value, default),
  fail-soft (bad/unrecognized values degrade to the caller's default,
  same posture compile.py's own _coerce() already documents for its
  numeric fields) -- as opposed to install_adapter.py's install.yaml
  flags, which correctly raise hard, appropriate for a one-time CLI
  install but not for config read on every session/render.
- _review_config() (proposals.py) now normalizes auto_approve_on_receipt
  once at the read boundary -- the single source of truth every caller
  already funnels through -- rather than patching each read site
  individually.
- 8 config loaders switched from bool(raw.get(...)) to coerce_bool(...).
- New tests/test_config_coerce.py for the coercer itself, plus a quoted-
  false regression test added to every affected module's existing test
  file, plus a quoted-true test in test_receipt_auto_approve.py proving
  the fix doesn't break the legitimate quoted case.

Not included: openclaw/types.py's CompactParams.force field uses the same
bool(raw.get(...)) shape but parses a JSON wire payload from an external
host (OpenClaw), not YAML config -- JSON has native booleans, so the
quoted-string trap doesn't apply the same way, and it's a different root
cause than the rest of this sweep.
CodeRabbit review comment: src/vouch/** comments and review notes must be
lowercase per path instructions. Fixes 3 capitalized sentence-starts
introduced in the previous commit:

- config_coerce.py:1 (module docstring)
- config_coerce.py: coerce_bool() docstring
- proposals.py: _review_config() docstring, two sentence-starts
@Tet-9
Tet-9 force-pushed the fix/compile-two-phase-quoted-false-yaml branch from ef210b4 to 52358dc Compare July 27, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

retrieval context, search, synthesis, and evaluation size: M 200-499 changed non-doc lines storage kb storage, migrations, schemas, and proposals tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant