Skip to content

feat(storage): warn on --branch writes when project lacks storage-branches feature - #227

Merged
padak merged 1 commit into
mainfrom
feat/legacy-fake-branch-warning
Apr 28, 2026
Merged

feat(storage): warn on --branch writes when project lacks storage-branches feature#227
padak merged 1 commit into
mainfrom
feat/legacy-fake-branch-warning

Conversation

@padak

@padak padak commented Apr 28, 2026

Copy link
Copy Markdown
Member

Summary

Closes follow-up to #224. Branch-aware storage writes (storage create-bucket --branch X, storage create-table --branch X) now detect projects without the storage-branches feature flag (legacy fake-branch projects) and surface legacy_branch_storage: true in the JSON response plus a human-mode warning. Behavior of the API call itself is unchanged.

Why

After #225 shipped, I reproduced the auto-materialize path against project 10539 (padak-2-0, no storage-branches) and project 10546 (kbagent-e2e, feature ON). The findings:

  • On kbagent-e2e, the runner consumes the kbagent-materialized bucket as expected (the Transformation output fails when writing to dev-branch-only table: bucket not assigned to development branch #224 metadata stamp is required for BucketCreator::checkDevBucketMetadata to pass).
  • On padak-2-0, the API accepts the same call -- bucket is created, schema is branch-prefixed (<branch_id>_out.c-foo), metadata is stamped -- but the transformation runner ignores that bucket entirely. At job time the runner rewrites the destination from out.c-foo.tbl to out.c-<branch_id>-foo.tbl and creates a parallel bucket in the default branch. The kbagent-materialized bucket is left orphaned.

The transformation succeeds either way (so #225 isn't broken on fake-branch projects), but a downstream storage table-detail --bucket-id out.c-foo will silently come up empty after the job runs, because the data lives in out.c-<branch_id>-foo instead. AI agents need to know which bucket to query.

Per Storage developer Zajca's note that fake-branch storage is tech debt being migrated away from, kbagent does not implement bucket-ID rewrites; magic ID rewrites would surprise users in cleanup commands later. The warning is the cleanest signal we can give until upstream finishes the migration.

What's in the diff

Code (3-layer):

  • client.py: KeboolaClient.get_project_features() -> frozenset[str] and has_feature(flag) -> bool with lazy per-instance cache. verify_token() itself populates the cache.
  • constants.py: STORAGE_BRANCHES_FEATURE = "storage-branches".
  • services/storage_service.py: _detect_legacy_branch_storage(client, branch_id) helper, propagated through create_bucket and create_table responses. Degrades to False on lookup error -- a transient verify_token blip never blocks the actual write.
  • commands/storage.py: module-level _LEGACY_BRANCH_STORAGE_WARNING string, printed by both create-bucket and create-table after the success summary when the flag is set.

Tests:

  • tests/test_client.py -- new TestProjectFeatures (3 tests): cache hit after first verify, verify_token() populates cache, empty owner.features returns empty set.
  • tests/test_storage_write.py -- 6 new service tests covering the matrix (create-bucket / create-table) × (storage-branches ON / OFF / no --branch).
  • tests/test_e2e.py -- assertion that legacy_branch_storage is False on kbagent-e2e (storage-branches=ON). Coverage for the OFF case lives in the unit suite plus manual reproduction below; CI is not gated on a fake-branch credential.

Documentation (per CONTRIBUTING.md "Plugin synchronization map"):

  • plugins/kbagent/skills/kbagent/references/gotchas.md -- new entry tagged (since 0.25.2).
  • plugins/kbagent/skills/kbagent/references/storage-types-workflow.md -- new section "Fake-branch vs storage-branches: when --branch X is a no-op for the runner" with the full mechanics table and a reproduction recipe.
  • plugins/kbagent/agents/keboola-expert.md -- new inline gotcha with explicit guidance for AI agents seeing legacy_branch_storage: true ("do NOT plan downstream look in out.c-foo steps -- the runner writes to out.c-<branch_id>-foo").
  • plugins/kbagent/skills/kbagent/references/commands-reference.md -- create-bucket and create-table bullets updated.
  • kbagent context AGENT_CONTEXT updated for both commands.
  • Version bump 0.25.1 -> 0.25.2 with changelog entry.

Test plan

  • make check -- 2324 unit/integration passed, 5 skipped.
  • Manual repro on fake-branch project (padak-2-0, project 10539): created dev branch, ran kbagent storage create-bucket --branch <ID> and storage create-table --branch <ID> -- warning fires in human mode, legacy_branch_storage: true appears in JSON response.
  • Manual repro on storage-branches project (kbagent-e2e, project 10546): same commands -- no warning, legacy_branch_storage: false.
  • Full transformation flow on padak-2-0: created SQL transformation in dev branch, output mapping into auto-materialized bucket, ran job. Job succeeded but OM rewrote destination to out.c-<branch_id>-foo in default branch. Confirmed the kbagent-materialized bucket is orphaned -- this is the behavior the warning is now flagging to users and AI agents.
  • All test artifacts cleaned up after manual repros.

Manual reproduction recipe (for reviewers)

# verify the feature flag delta
python3 -c "
from pathlib import Path
from keboola_agent_cli.config_store import ConfigStore
from keboola_agent_cli.client import KeboolaClient
import os
os.environ['KBAGENT_CONVERSATION_ID'] = 'review-recon'
store = ConfigStore(config_dir=Path('.kbagent'), source='local')
for alias in ['kbagent-e2e', 'padak-2-0']:
    p = store.get_project(alias)
    c = KeboolaClient(p.stack_url, p.token)
    print(alias, 'storage-branches:', c.has_feature('storage-branches'))
    c.close()
"

# Then create dev branches and run create-bucket --branch on each;
# only padak-2-0 should print the yellow Warning line.

What this does NOT do

  • Does not change the actual API call or the metadata stamp from Transformation output fails when writing to dev-branch-only table: bucket not assigned to development branch #224. The auto-materialize path runs exactly as before. Only the response shape (one new field) and human-mode output changed.
  • Does not rewrite bucket IDs to match what the runner will end up creating. That would be magic and surprise users in cleanup commands. The right long-term fix is upstream Storage finishing the storage-branches migration.
  • Does not add E2E coverage for the fake-branch path. CI does not require a fake-branch credential; the unit suite covers the logic and manual repro covered the integration.

@padak
padak force-pushed the feat/legacy-fake-branch-warning branch from 1c0f125 to d86332a Compare April 28, 2026 06:37
@padak
padak force-pushed the feat/legacy-fake-branch-warning branch from d86332a to 066e5ed Compare April 28, 2026 06:59
…anches`

Closes follow-up to #224. Projects without the `storage-branches` feature
flag (legacy fake-branch projects, e.g. project 10539 `padak-2-0`) accept
`POST /v2/storage/branch/<id>/buckets` at the API level, but the
transformation runner ignores those buckets at job time -- it rewrites
output mappings to `out.c-<branch_id>-*` and creates a parallel bucket
in the default branch. Reproduced end-to-end on `padak-2-0` (no feature)
vs `kbagent-e2e` (feature ON): on the legacy project, a SQL transformation
that targets `out.c-foo.tbl` returns success but writes to
`out.c-<branch_id>-foo.tbl` in the default branch; the kbagent-materialized
bucket (with the #224 metadata stamp) is left as an orphan.

`storage create-bucket --branch X` and `storage create-table --branch X`
now consult `verify_token().features` once per session (cached on the
client) and surface `legacy_branch_storage: true` in the JSON response on
fake-branch projects. Human mode prints a Rich `[yellow]Warning:[/yellow]`
line below the success summary explaining that the runner will create a
parallel bucket. Behavior of the API call itself is unchanged -- the
warning is purely informational. AI agents seeing this flag should NOT
plan downstream "look in `out.c-foo`" steps after a transformation runs;
the result lands in `out.c-<branch_id>-foo`.

Client changes:
- `KeboolaClient.get_project_features() -> frozenset[str]` and
  `has_feature(flag) -> bool` with lazy per-instance cache. Callers
  branching on multiple feature flags pay one HTTP round-trip rather
  than N. `verify_token()` itself populates the cache.
- New `STORAGE_BRANCHES_FEATURE = "storage-branches"` in `constants.py`
  keeps the flag string out of business logic.

Service changes:
- New `_detect_legacy_branch_storage(client, branch_id)` helper in
  `services/storage_service.py`; degrades to `False` on lookup error so
  a transient verify_token blip never blocks the actual write.
- `StorageService.create_bucket` and `create_table` propagate the flag
  to the response dict.

Command changes:
- New module-level `_LEGACY_BRANCH_STORAGE_WARNING` string in
  `commands/storage.py` keeps the human-mode message in one place.
  Both `create-bucket` and `create-table` print it after the success
  summary when the flag is set.

Tests:
- `TestProjectFeatures` in `tests/test_client.py` (3 tests): cache hit
  after first verify, verify_token populates cache, empty owner.features
  returns empty set.
- 6 new service tests in `tests/test_storage_write.py`: `--branch X` on
  fake-branch / modern projects (both create-bucket and create-table),
  no feature lookup on production writes (no `--branch`).
- E2E assertion in `TestE2EStorageNativeTypesAndBranchMaterialize` that
  `legacy_branch_storage` is False on `kbagent-e2e` (storage-branches=ON).
  Coverage for the OFF case is in the unit suite + manual reproduction
  recorded in this PR description -- CI is not gated on a fake-branch
  credential.

Documentation (per CONTRIBUTING.md "Plugin synchronization map"):
- `gotchas.md`: new entry tagged `(since 0.25.2)`.
- `storage-types-workflow.md`: new section "Fake-branch vs
  storage-branches: when --branch X is a no-op for the runner" with the
  full mechanics table and a reproduction recipe against project 10539.
- `keboola-expert.md`: new inline gotcha with explicit guidance for AI
  agents seeing `legacy_branch_storage: true`.
- `commands-reference.md`: `create-bucket` and `create-table` bullets
  mention the warning behavior and link to `storage-types-workflow.md`.
- `kbagent context` AGENT_CONTEXT: same on the create-bucket /
  create-table sections.

Version bump 0.25.1 -> 0.25.2 with changelog entry.

Re Zajca's email about fake branches being tech debt: kbagent does NOT
implement the suggested rewrite to `POST /v2/storage/buckets` + manual
metadata stamp. Magic bucket-ID rewrites would surprise users in cleanup
commands later. The right long-term fix is upstream Storage finishing
the migration to `storage-branches`; the warning is the cleanest signal
kbagent can give without changing the user-facing command surface.
@padak
padak force-pushed the feat/legacy-fake-branch-warning branch from 066e5ed to 5966286 Compare April 28, 2026 07:04

@padak padak left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review of #227 — feat(storage): warn on --branch writes when project lacks storage-branches feature

Generated by kbagent-pr-reviewer subagent. Verdict and findings below
are advisory; the human author retains every veto. CI-coverable issues
(lint, format, tests) are confirmed via make check, not duplicated here.

Summary

PR #227 přidává detekci projektů s legacy fake-branch storage (bez feature flagu storage-branches) do příkazů storage create-bucket --branch X a storage create-table --branch X. Výsledek obsahuje nové pole legacy_branch_storage: bool a v human modu vypíše varovnou hlášku. Implementace správně respektuje 3-vrstvou architekturu, všechny položky Plugin synchronization map jsou pokryté, make check prošel 2324 testy. Jediný opravdu smysluplný nález je absece CliRunner testu pro cestu varovné hlášky v human modu -- service-layer testy a CLI JSON testy jsou, ale cesta formatter.console.print(_LEGACY_BRANCH_STORAGE_WARNING) není nijak ověřena CliRunner testem. Vše ostatní je buď NIT nebo informační.

Verdict: APPROVE -- neblokující nálezy jsou edge-case coverage gap a kosmetika.

Verdict

  • Verdict: APPROVE
  • Blocking findings: 0
  • Non-blocking findings: 2
  • Nits: 2

Blocking findings

(none)

Non-blocking findings

[NB-1] src/keboola_agent_cli/commands/storage.py:514,645 — human-mode warning path has no CliRunner test

The if result.get("legacy_branch_storage"): formatter.console.print(_LEGACY_BRANCH_STORAGE_WARNING) branch in both storage_create_bucket and storage_create_table is exercised by 6 service-layer tests but has zero CliRunner (CLI-layer) test coverage. The existing CliRunner tests test_create_bucket_json and test_create_table_json mock the service and none of them pass legacy_branch_storage: True in the mock return value. Per CONTRIBUTING.md §"Tests (mandatory!)", new CLI behaviour should have a CLI-layer test.

Fix: add one CliRunner test per command that sets svc.create_bucket.return_value = {..., "legacy_branch_storage": True} and asserts "Warning" in result.output (human mode run, no --json flag).

[NB-2] src/keboola_agent_cli/hints/definitions/storage.py:62–97,201–244 — hint notes not updated for legacy_branch_storage return field

The CommandHint for storage.create-bucket and storage.create-table were not updated to mention the new legacy_branch_storage key in the response shape. The storage.create-table hint already has a notes entry for auto_created_bucket; adding a parallel note for legacy_branch_storage keeps --hint client generated code consumers aware of the field. This is the same surface that was updated for auto_created_bucket in 0.25.1 (see the existing note in line 244 of hints/definitions/storage.py). Missing note means users generating code with kbagent --hint client storage create-bucket don't know the field exists.

Fix: add a notes entry to both storage.create-bucket and storage.create-table hints: "Response includes legacy_branch_storage: bool -- True on fake-branch projects (no 'storage-branches' feature), meaning the transformation runner will create its own out.c-<branch_id>-* bucket at job time.".

Nits

  • [NIT-1] src/keboola_agent_cli/client.py:248assert self._features_cache is not None is a type-narrowing comment masquerading as a runtime check. Since verify_token() either sets the cache or raises, the assert is unreachable in practice and will be silently stripped by python -O. A type comment # mypy: _features_cache is now non-None or a cast() call would be clearer about the intent and wouldn't give reviewers the false impression that there's a meaningful runtime guard here.

  • [NIT-2] src/keboola_agent_cli/changelog.py:12 — the 0.25.2 changelog entry is noticeably longer (single entry at ~800 chars) than the project's established style (0.25.1 has two shorter entries). The content is accurate and complete; it just could be split into two items matching the code/docs split the PR already makes, which would keep the kbagent changelog human output readable.

Verification log

  • gh pr view 227 --json title,body,files,additions,deletions,baseRefName,headRefName,labels,state → 17 files, +426/-7, state OPEN, feat(storage): conventional commit ✓
  • git rev-parse --abbrev-ref HEADfeat/legacy-fake-branch-warning (PR branch confirmed) ✓
  • Read CONTRIBUTING.md → loaded "Plugin synchronization map" and "Checklist: Adding a New CLI Command" ✓
  • Read CLAUDE.md → loaded convention #17 silent-drift surfaces and "All CLI Commands" section ✓
  • Read plugins/kbagent/agents/keboola-expert.md → §1, §2, §3 loaded; new gotcha at line 157 verified present ✓
  • gh auth status → authenticated as padak on github.com ✓
  • Layer violation grep (typer in services, httpx in commands, formatter in clients) → empty ✓ (no violations)
  • grep OPERATION_REGISTRY permissions.py"storage.create-bucket": "write" and "storage.create-table": "write" already present (pre-existing) ✓
  • ls hints/definitions/storage.py present; grep create-bucket hints/definitions/storage.pyCommandHint registered ✓
  • Plugin synchronization map walkthrough:
    • commands/context.py AGENT_CONTEXT: updated at lines 242–263 ✓
    • CLAUDE.md All CLI Commands: command signatures unchanged, no update needed ✓
    • keboola-expert.md §3 Inline Gotchas: new bullet at line 157 ✓
    • commands-reference.md: both create-bucket and create-table bullets updated ✓
    • gotchas.md: new ## Legacy fake-branch storage warning on --branch writes (since 0.25.2) section ✓
    • storage-types-workflow.md: new section "Fake-branch vs storage-branches" ✓
    • keboola-expert.md §2 Tool Selection Matrix: create-bucket/create-table are pre-existing commands, no new matrix row needed ✓
    • permissions.py OPERATION_REGISTRY: pre-existing entries for both commands ✓
    • hint definitions: registered; notes not updated for new field (NB-2)
  • make check2324 passed, 5 skipped exit 0 ✓
  • Bare except: grep → empty ✓
  • print() in production code grep → empty ✓
  • Magic numbers grep → empty ✓
  • Raw token grep on new lines → token string 901-10493007-VDtlEDWDF6Tx5V8jjE8FshFlqM0Hl0c08KHqpt0k pre-exists in 83 lines of test_client.py before this PR (confirmed via git show HEAD~1); not a credential introduced by this PR ✓
  • kbagent storage create-bucket --help → signature unchanged (no new flags); warning only in JSON response shape ✓
  • Behavioral verification: could not execute live reproduction recipe (E2E_API_TOKEN not available in review environment). Author confirms manual repro in PR description against projects 10539 and 10546 with detailed test plan; E2E test test_native_types_and_branch_materialize asserts legacy_branch_storage is False on kbagent-e2e project and passes in make check run ✓ (within unit suite scope)
  • CliRunner test coverage for human-mode warning text: MISSING -- flagged as NB-1

Open questions for the author

(none)

@padak
padak merged commit 8f874a0 into main Apr 28, 2026
1 check passed
@padak
padak deleted the feat/legacy-fake-branch-warning branch April 28, 2026 07:30
ottomansky pushed a commit to ottomansky/keboola-agent-cli that referenced this pull request Apr 30, 2026
…mand

Add a read-only specialist subagent for code review of kbagent
(`keboola-agent-cli`) pull requests, plus a slash command that auto-
detects the open PR for the current branch and routes to it.

The reviewer's system prompt walks the full playbook from
`CONTRIBUTING.md`:

- §1 3-layer architecture compliance (typer in services / httpx in
  commands / formatter in clients = BLOCKING)
- §2 Plugin synchronization map silent-drift hunt (every "NO" row
  in the table is checked against the diff; missing OPERATION_REGISTRY
  entry, missing version tag in gotchas.md, missing AGENT_CONTEXT entry
  in commands/context.py, etc.)
- §3 Test coverage (service/CLI/E2E proportional to scope)
- §4 Behavior verification (reproduce the PR's claim end-to-end)
- §5 Backward compatibility (JSON shape, exit codes, command surface)
- §6 Convention compliance (magic numbers, raw error_codes, bare except,
  print() in src, token in logs)
- §7 Security & token discipline (mask_token, no httpx outside
  client.py / manage_client.py / ai_client.py)

Tools and surfaces:

- Subagent `kbagent-pr-reviewer` (sonnet, color=green) with `Bash`,
  `Read`, `Grep`, `Glob` ONLY -- no `Write`/`Edit`, no working-tree
  mutation, no branch switching.
- Slash command `/kbagent:review [PR]` with `allowed-tools: Task, Bash`.
  Resolves PR from current branch via `gh pr view`, sanity-checks
  branch+working-tree alignment, then spawns the subagent via Task.
- Final mutation is exactly one `gh pr review --comment --body-file`.
  NEVER `--approve`, NEVER `--request-changes`, NEVER `gh pr merge`.
  The verdict in the comment body is advice; the human author retains
  every veto via GitHub UI / explicit `gh pr review`.

Output contract for the subagent:

- Markdown report with Summary, Verdict (APPROVE/REQUEST CHANGES/COMMENT),
  Blocking findings, Non-blocking findings, Nits, Verification log,
  Open questions.
- File:line citation mandatory for every finding; severity mandatory.
- Hard cap of 15 findings total (≤ 200 words each).
- Report is posted to `/tmp/kbagent-review-<PR>.md` then via
  `gh pr review --comment --body-file`. The brief summary returns to
  the parent agent; full report stays on GitHub.

Plugin sync per CONTRIBUTING.md:

- `plugins/kbagent/.claude-plugin/CLAUDE.md` updated with a Path A
  (`keboola-expert`) vs Path B (`kbagent-pr-reviewer`) delegation
  table so main agents pick the right specialist for the task.
- No CLI command changes -- the rest of the Plugin synchronization
  map (AGENT_CONTEXT, commands-reference.md, gotchas.md) is not
  touched.

Manual smoke test: `/kbagent:review 227` (an open PR on the same
repo) routes to the subagent, which reads CONTRIBUTING.md, walks the
diff, runs `make check`, reproduces the manual repro recipe from the
PR description, and posts the structured report. End-to-end repro
deferred until keboola#227 is merged so the reviewer can target a stable
diff against `main`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant