Skip to content

fix(genbi): treat non-dict apps.yml entries as missing - #2583

Closed
Bartok9 wants to merge 3 commits into
Canner:mainfrom
Bartok9:fix/genbi-get-app-non-dict-entry
Closed

fix(genbi): treat non-dict apps.yml entries as missing#2583
Bartok9 wants to merge 3 commits into
Canner:mainfrom
Bartok9:fix/genbi-get-app-non-dict-entry

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Hand-edited .wren/apps.yml can place a scalar under an app key; get_app returned it and callers crashed on mapping ops.

Changes

  • get_app returns None for non-dict entries
  • update_app raises KeyError instead of AttributeError

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of malformed application records.
    • Application listings and lookups now skip invalid entries instead of failing.
    • Registering an application repairs malformed existing records.
    • Updating missing or invalid applications now returns a clear error.

@github-actions github-actions Bot added python Pull requests that update Python code core labels Jul 25, 2026
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8cdd519e-2ef0-4b2d-8d07-6102744e99e8

📥 Commits

Reviewing files that changed from the base of the PR and between 721ba0f and d3b1718.

📒 Files selected for processing (1)
  • core/wren/tests/unit/test_genbi_index.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/wren/tests/unit/test_genbi_index.py

Walkthrough

The GenBI app index now handles non-dictionary entries during lookup, registration, updates, and listing. Unit tests cover malformed entries, valid entries, persistence, replacement, rejection, and CLI output.

Changes

App Entry Validation

Layer / File(s) Summary
Guard app access and validate lookups
core/wren/src/wren/genbi/index.py, core/wren/tests/unit/test_genbi_index.py
get_app() returns None for malformed entries. update_app() raises KeyError for missing or malformed entries. Tests cover valid and invalid cases.
Repair malformed registrations
core/wren/src/wren/genbi/index.py, core/wren/tests/unit/test_genbi_index.py
register_app() replaces non-dictionary entries with valid default records. Tests verify persistence.
Skip malformed entries during listing
core/wren/src/wren/genbi/cli.py, core/wren/tests/unit/test_genbi_index.py
list_apps skips non-dictionary entries. Tests verify valid output and successful CLI completion.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Poem

A rabbit checks each app record in the nest,
Replaces malformed data and runs a test.
Missing apps raise keys when updates begin,
Listing skips bad entries and shows valid ones within.
The index stays orderly from end to end.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the change but omits the required failure reproduction, actual error output, testing details, and duplicate check. Add the observed failure with actual error output, identify the tests and CI coverage, and list the open PRs checked for duplicates.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the genbi fix for treating non-dictionary apps.yml entries as missing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
core/wren/tests/unit/test_genbi_get_app_guard.py (1)

8-13: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for update_app()’s new contract.

This test validates get_app() only, while the PR also changes update_app() to raise KeyError for missing or non-dictionary entries. Add cases for malformed, missing, and valid entries, including persistence of the valid update.

🤖 Prompt for 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.

In `@core/wren/tests/unit/test_genbi_get_app_guard.py` around lines 8 - 13, Add
tests for update_app’s new contract alongside test_get_app_skips_non_dict_entry:
assert KeyError for both missing and non-dictionary app entries, and verify a
valid dictionary entry updates successfully and persists when reloaded through
get_app.
🤖 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.

Nitpick comments:
In `@core/wren/tests/unit/test_genbi_get_app_guard.py`:
- Around line 8-13: Add tests for update_app’s new contract alongside
test_get_app_skips_non_dict_entry: assert KeyError for both missing and
non-dictionary app entries, and verify a valid dictionary entry updates
successfully and persists when reloaded through get_app.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 179b6acf-b8ae-4817-8ee2-3795453bbf15

📥 Commits

Reviewing files that changed from the base of the PR and between d472877 and 35143b5.

📒 Files selected for processing (2)
  • core/wren/src/wren/genbi/index.py
  • core/wren/tests/unit/test_genbi_get_app_guard.py

@goldmedal

Copy link
Copy Markdown
Collaborator

Review

Real bug, right direction, nicely restrained diff, CI green. My concern is that the fix is too narrow: two sibling paths in the same file share the same root cause and are untouched — and the new get_app behaviour actively routes the user into one of them. I'd like those covered before merge.


1. register_app has the identical bug, and this PR makes it easier to hit

index.py:82 uses or, so a truthy non-dict (a string) falls straight through:

entry = index["apps"].get(name) or {...}
entry["data_mode"] = data_mode   # 'str' object does not support item assignment

Reproduced against apps: {bad: "not-a-map"}:

register_app: TypeError: 'str' object does not support item assignment

The problem is that this is exactly the recovery path the CLI advertises. With get_app fixed: wren genbi verify bad_require_registered (cli.py:229-240) prints app 'bad' is not registered. Run 'wren genbi register bad' first. → the user does that → bare TypeError traceback. We've traded crash A for a signpost pointing at crash B.

(Falsy non-dicts — bad:, bad: 0, bad: [] — fall back to a fresh entry and are fine. Only truthy non-dicts blow up.)

2. wren genbi list shares the root cause and is likely the first command hit

cli.py:205:

deploy = entry.get("deploy") or {}   # 'str' object has no attribute 'get'

Confirmed AttributeError. This path reads load_index(...)["apps"] directly, bypassing get_app, so the PR doesn't cover it. The description says "callers crashed on mapping ops" — list is precisely such a caller, and it's the most natural thing to run after a hand-edit.

3. The module now holds two opposite stances on malformed data

load_index raises MalformedIndexError with the offending path for a non-mapping top level and a non-mapping apps (index.py:47-68). This PR makes a non-dict entry silently None, which the CLI reports as "not registered".

Describing a hand-broken entry as "not registered" is misleading, and the suggested remedy either crashes (today) or — once register_app is fixed — silently overwrites the user's hand-edited content. The stance consistent with the rest of the module is to raise MalformedIndexError here too, naming which key in .wren/apps.yml is bad. The exception type already exists.

If you'd rather keep the lenient behaviour so one bad entry can't take down the whole listing, that's defensible — but then #2 genuinely needs fixing, otherwise the rationale doesn't hold.


4. Minor: bare KeyError(name) in update_app conflates two states

"Not registered" and "registered but malformed" both become KeyError(name); callers can't tell them apart. Nothing reaches it today (verify/deploy both go through _require_registered first), but deploy has a narrow window: cli.py:322 reads the index → verify + network deploy → cli.py:373 update_app re-reads it. If the file changes in between, the KeyError surfaces as a bare traceback after a successful deploy (only the get_provider call is wrapped in except KeyError).

The change itself (AttributeErrorKeyError) is an improvement; MalformedIndexError would be more precise and would avoid the semantic collision.

5. Minor: tests

  • Duplicate file. test_genbi_get_app_guard.py overlaps test_genbi_index.py, which already asserts on get_app at :163-165. Worth folding in rather than adding a file.
  • update_app's new contract is untested — the CodeRabbit nitpick is correct here.
  • Two unused imports, plus formatting:
    test_genbi_get_app_guard.py:3:8  F401 `yaml` imported but unused
    test_genbi_get_app_guard.py:5:39 F401 `wren.genbi.index.load_index` imported but unused
    ruff format: 1 file would be reformatted   (line 9 exceeds 88 chars)
    
    CI is green because the lint job only covers src/ (wren-ci.yml:26-28) — tests/ isn't linted at all. So this isn't "CI missed it, no harm"; CI structurally can't see it. Might be worth a follow-up to bring tests/ under lint.
  • Missing -> None annotations, which the existing tests in test_genbi_index.py all carry. Not enforced (select = ["E", "F", "I", "PLC"]), style only.

Suggested before merge

  1. Same-root-cause fix in register_app
  2. Fix list_apps
  3. Drop the unused imports; fold the test into test_genbi_index.py and add update_app coverage

#3 above is a design call — worth deciding explicitly whether a malformed entry should be silently ignored or loudly reported. Either is workable, but the module should pick one and apply it consistently.

Address goldmedal review on Canner#2583: register_app no longer TypeErrors on
truthy non-dict entries, list skips malformed entries, fold get_app guard
tests into test_genbi_index and cover update_app contract.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@core/wren/tests/unit/test_genbi_index.py`:
- Around line 210-223: Update test_register_app_replaces_non_dict_entry to
reload the saved index after register_app completes, then assert apps["bad"]
contains the replacement entry and its expected fields, confirming the repair
persisted to .wren/apps.yml in addition to validating the returned dictionary.
- Around line 251-274: Update test_list_skips_non_dict_entries to assert that
the malformed app name "bad" is absent from result.output, alongside the
existing assertions for successful listing and valid app output, verifying
list_apps excludes skipped malformed entries.
🪄 Autofix

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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c0ca78b-521c-41d7-b579-a485ccad8822

📥 Commits

Reviewing files that changed from the base of the PR and between 35143b5 and 721ba0f.

📒 Files selected for processing (3)
  • core/wren/src/wren/genbi/cli.py
  • core/wren/src/wren/genbi/index.py
  • core/wren/tests/unit/test_genbi_index.py

Comment thread core/wren/tests/unit/test_genbi_index.py
Comment thread core/wren/tests/unit/test_genbi_index.py
CodeRabbit: reload after register_app repair; assert list omits "bad".
@Bartok9

Bartok9 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — agreed the first pass was too narrow.

Addressed in 721ba0f / d3b1718:

  1. register_app — truthy non-dicts no longer fall through or; treated like missing and replaced with a fresh default entry (same repair path as a new register).
  2. list_apps — skips non-dict entries instead of calling .get on a scalar (lenient listing so one bad key doesn't take down the whole list).
  3. Tests — folded the guard coverage into test_genbi_index.py, added update_app contract cases, persistence reload after register repair, and assert that "bad" is absent from list output. Dropped the extra test file / unused imports.

Design call (#3): kept the lenient stance for individual app entries (get_app → None / “not registered”, list skips) rather than raising MalformedIndexError per key. Rationale: top-level / apps shape is structural and already hard-fails via MalformedIndexError; a single hand-broken entry shouldn't brick listing or every other app. register is the explicit recovery path and now overwrites the bad value with a valid record (documented risk of replacing hand-edited junk). Happy to flip to loud MalformedIndexError naming the key if you'd rather force a manual edit — easy follow-up either way.

On bare KeyError in update_app: left as-is for this PR (AttributeError → KeyError is still an improvement for the narrow race you noted); can switch to MalformedIndexError in a follow-up if you want the two states distinguished at the API boundary.

CI was green on the prior commit; re-running on the test tighten-up.

@goldmedal goldmedal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

CI is green and the tests do call real functions rather than matching source text, but two items block: this duplicates an open PR against the same file, and the list hardening doesn't actually hold.

Blocking

1. Duplicate PR against the same file. #2587 changes the same list_apps in core/wren/src/wren/genbi/cli.py for the same non-dict-entry bug. The contribution bar says duplicate PRs against the same file are closed without review. Fold them into one PR or withdraw one — and note #2587 is strictly better on that hunk, see (2).

2. The list_apps guard is incomplete — the same crash survives one level down. Verified on this branch: a valid dict entry whose deploy is a scalar still kills the command.

apps: {good: {source: apps/good, status: deployed, deploy: "https://x"}}

$ wren genbi list
AttributeError: 'str' object has no attribute 'get'    # exit 1, no output

deploy = entry.get("deploy") or {}deploy.get("last_url"). If the premise is "a hand-edited apps.yml must not crash the CLI", this PR does not deliver it. #2587 covers exactly this case ("Non-dict deploy blocks are treated as empty").

3. The update_app hunk is not a fix. Both CLI callers (verify, deploy) reach update_app only after _require_registered, which already guarantees a dict via get_app. Neither catches KeyError or AttributeError. So the change swaps one uncaught traceback for another — no user-visible difference. Under "Label honestly" this hunk is refactor:, not fix:.

4. Dead assertion. assert "Traceback" not in result.output cannot fail: Click's CliRunner puts the exception in result.exception and leaves output empty. Confirmed in the scenario that does crash (case 2) — result.output == '' and the assertion still passes. "Every assertion must be capable of failing." Drop it; assert result.exit_code == 0 is the real check.

Design

5. Validation is scattered instead of done at the edge, with four different policies. load_index is already this module's validation boundary — it raises MalformedIndexError naming the path for a non-mapping top level and for a non-mapping apps. This PR adds four more guards downstream, each with different semantics: get_app → silently None, list_apps → silently skip, register_app → silently overwrite, update_appKeyError. core/wren/.claude/CLAUDE.md says validate once, at the edge, and for a genuine input boundary "validate and report rather than crash".

For the shape to follow here, see 9bdae39 (#2604), merged after this branch was opened: the loader drops malformed entries silently and validate_project is the single place that reports them. One entry-level check inside load_index covers all four callers and matches that precedent.

6. Silent skip produces an inconsistent surface. With bad: "not-a-map", wren genbi list shows nothing, but wren genbi remove bad then succeeds and prints Removed bad from the index. The user is told it doesn't exist, then removes it. remove_app still uses name in index["apps"] and wasn't touched. At minimum report the bad key on stderr.

7. register_app silently discards hand-written data and prints the same Registered X message as a normal create, so there is no signal the entry was replaced.

Description

The states are reachable — confirmed on the base commit: register_appTypeError: 'str' object does not support item assignment, get_app → returns 'not-a-map', update_appAttributeError — but the description asserts this instead of showing it, which is what the pre-merge check flagged. Please paste the actual errors.

Also address the module docstring: it says apps.yml is "machine-written ... never hand-rolled", which is a recorded decision this PR reverses. load_index already contradicts it, so either argue it is wrong or update it.

Rebase

This branch is 33 commits behind main and its CI ran against a base that predates 9bdae39. It merges cleanly, but a textually clean merge is not a semantically correct one — please rebase and re-run.

What's good

The register_app fix is genuinely correct — falsy non-dicts were already handled by or, truthy ones were not. The get_app guard turns an AttributeError into a clean "not registered" message via _require_registered.

Bartok9 added a commit to Bartok9/WrenAI that referenced this pull request Aug 7, 2026
- list: report invalid entries; treat non-dict deploy as empty
- get_app: non-dict → None (not registered)
- register_app: replace truthy non-dict instead of TypeError
- update_app: KeyError on missing/non-dict

Addresses goldmedal review on Canner#2583/Canner#2587 (single PR).
@Bartok9

Bartok9 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Closing as duplicate of #2587 (goldmedal review: same-file genbi list/index hardening). Full fix lives on #2587.

@Bartok9 Bartok9 closed this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core python Pull requests that update Python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants