merge_bottles_runtime KeyErrors on a repo declared only by a later bottle #457

Closed
opened 2026-07-21 15:46:34 -04:00 by didericis-claude · 0 comments
Collaborator

merge_bottles_runtime raises KeyError whenever a bottle composed later declares a git-gate.repos entry that an earlier bottle does not.

Repro

from bot_bottle.manifest import ManifestBottle
from bot_bottle.manifest_extends import merge_bottles_runtime

repo = {"url": "ssh://git@h/o/r1.git",
        "key": {"provider": "gitea", "forge_token_env": "T", "api_url": "https://x"}}
base = ManifestBottle.from_dict("a", {"env": {"X": "1"}})
override = ManifestBottle.from_dict("b", {"git-gate": {"repos": {"r1": repo}}})
merge_bottles_runtime([base, override])   # KeyError: 'r1'

In practice this is ./cli.py start <agent> --bottle claude-dev --bottle bot-bottle-dev, where only the second bottle carries repos:

  File "bot_bottle/manifest_extends.py", line 44, in <genexpr>
    override_repos_by_name.get(n, base_repos_by_name[n])
                                  ~~~~~~~~~~~~~~~~~~^^^
KeyError: 'bot-bottle'

Cause

_merge_two_bottles_runtime builds the union of repo names, then resolves each one with

override_repos_by_name.get(n, base_repos_by_name[n])

Python evaluates a .get() default eagerly, so base_repos_by_name[n] is indexed for every name — including names that exist only in the override. The union logic directly above is correct; only the lookup is wrong.

Impact

Bottle order becomes load-bearing for no stated reason: reversing the two --bottle flags works around it. The merge-order rules documented on merge_bottles_runtime say nothing about repos having to appear in the first bottle.

Fix

Pick from the override first and fall back lazily, e.g.

override_repos_by_name[n] if n in override_repos_by_name else base_repos_by_name[n]

plus a regression test in tests/unit/test_manifest_bottle_merge.py covering an override-only repo (the existing coverage only exercises names present in the base).

Found while composing bottles for the #392 nested-containers verification; unrelated to that change and present on main.

`merge_bottles_runtime` raises `KeyError` whenever a bottle composed *later* declares a `git-gate.repos` entry that an earlier bottle does not. ## Repro ```python from bot_bottle.manifest import ManifestBottle from bot_bottle.manifest_extends import merge_bottles_runtime repo = {"url": "ssh://git@h/o/r1.git", "key": {"provider": "gitea", "forge_token_env": "T", "api_url": "https://x"}} base = ManifestBottle.from_dict("a", {"env": {"X": "1"}}) override = ManifestBottle.from_dict("b", {"git-gate": {"repos": {"r1": repo}}}) merge_bottles_runtime([base, override]) # KeyError: 'r1' ``` In practice this is `./cli.py start <agent> --bottle claude-dev --bottle bot-bottle-dev`, where only the second bottle carries repos: ``` File "bot_bottle/manifest_extends.py", line 44, in <genexpr> override_repos_by_name.get(n, base_repos_by_name[n]) ~~~~~~~~~~~~~~~~~~^^^ KeyError: 'bot-bottle' ``` ## Cause `_merge_two_bottles_runtime` builds the union of repo names, then resolves each one with ```python override_repos_by_name.get(n, base_repos_by_name[n]) ``` Python evaluates a `.get()` default **eagerly**, so `base_repos_by_name[n]` is indexed for *every* name — including names that exist only in the override. The union logic directly above is correct; only the lookup is wrong. ## Impact Bottle order becomes load-bearing for no stated reason: reversing the two `--bottle` flags works around it. The merge-order rules documented on `merge_bottles_runtime` say nothing about repos having to appear in the first bottle. ## Fix Pick from the override first and fall back lazily, e.g. ```python override_repos_by_name[n] if n in override_repos_by_name else base_repos_by_name[n] ``` plus a regression test in `tests/unit/test_manifest_bottle_merge.py` covering an override-only repo (the existing coverage only exercises names present in the base). Found while composing bottles for the #392 nested-containers verification; unrelated to that change and present on `main`.
didericis-claude added the
Priority
Medium
3
Kind/Bug
labels 2026-07-21 15:46:34 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: didericis/bot-bottle#457