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

    • Configuration boolean values are now parsed consistently across admission, capture, compilation, inbox, recall, session splitting, volunteer context, and receipt auto-approval settings.
    • Quoted values like "false" no longer incorrectly enable features (and "true" no longer becomes ambiguous).
    • Common true/false spellings and surrounding whitespace are normalized, while unknown values fall back to the configured defaults.
  • Tests

    • Added regression coverage to ensure quoted boolean strings and coercion behavior are interpreted correctly across the affected settings.

@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

Warning

Review limit reached

@Tet-9, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dac0abdc-79c7-4e1f-8ae5-0a1ec13d400e

📥 Commits

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

📒 Files selected for processing (2)
  • src/vouch/config_coerce.py
  • src/vouch/proposals.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: Introduced configuration namespaces updated by this change.
  • vouchdev/vouch#486: Added receipt-gated auto-approval behavior using the normalized review setting.
  • vouchdev/vouch#525: Refactored receipt auto-approval logic using the updated configuration flag.

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 matches the main change: fixing quoted YAML booleans across multiple 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
…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.
@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.

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 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

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

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