feat(0.43.3): kbagent update --beta + tag-pinned install URL - #317
Conversation
PEP 440 + GitHub --prerelease flag = two independent gates that keep stable users safe from accidentally landing on a beta release. Default path (unchanged): - _fetch_kbagent_latest_version() hits /releases/latest -- GitHub defines this endpoint as "the most recent non-prerelease, non-draft release", so betas marked --prerelease are invisible to the auto-update startup hook. - build_kbagent_upgrade_command() returns the same uv tool install --upgrade ... cmd as before -- uv defaults to rejecting PEP 440 pre-release version specs. Beta opt-in (kbagent update --beta or KBAGENT_INCLUDE_PRERELEASE=1): - Version fetcher switches to /releases (plural), filters drafts, parses every tag through packaging.version.Version, returns the highest by PEP 440 ordering. Stable hot-fixes to an older release line still win over a more-recently-tagged beta on a newer line. - Resolver opt-in: --prerelease=allow (uv) / --pre (pip) inserted into the install command so the resolver accepts 0.43.0b1 even though it's a pre-release. UX choices: - Ad-hoc only -- no release_channel: beta persistent config setting. Each opt-in is an active choice (--beta or env var per shell). This prevents the "I once typed --beta six months ago and forgot" foot-gun. - env var KBAGENT_INCLUDE_PRERELEASE=1 supports the CI-smoke-test use case (don't want to re-type --beta in every cron job step). - Startup auto-update hook untouched -- it still never auto-installs a beta. Only the explicit kbagent update --beta does. Tests: 10 new in test_version_service.py: - TestFetchKbagentLatestVersion: default uses /releases/latest endpoint; prerelease uses /releases and returns highest by PEP 440 ordering; prerelease skips drafts; falls back to stable when no betas; ignores invalid tags; HTTP failure returns None. - TestBuildKbagentUpgradeCommand: uv flag insertion (with and without [server] extras), pip --pre fallback. Docs: - CONTRIBUTING.md: new "Releasing a beta (pre-release) version" section documenting the PEP 440 + gh release create --prerelease workflow, the two-gate model, and the two opt-in paths. - CLAUDE.md `## All CLI Commands`: `version [--beta]` and `update [--beta]` with a one-line note about the env var override. - context.py: per-command help refreshed. - commands-reference.md: same. - changelog.py: 0.42.0 entry (mixed with the agent CLI feature in the same release).
CLAUDE.md `## Versioning` section gains a `### Beta / pre-release versions (since 0.42.0)` subsection covering: - Why PEP 440 (`0.43.0b1`) instead of SemVer (`-beta.1`) - The two independent gates (pre-release suffix + GitHub --prerelease) - Author workflow (4-step recipe from version bump to gh release) - User opt-in paths (--beta flag, env var, no persistent setting) - Cross-reference to CONTRIBUTING.md for the full author checklist CONTRIBUTING.md `## Releasing a new version` intro gains a blockquote signposting the beta workflow so authors discover the option without having to scroll to the end of the section.
The v0.42.0 implementation of --beta opt-in had a latent bug that
only fires when a beta tag lives on a feature branch (not main):
build_kbagent_upgrade_command(prerelease=True) generated
uv tool install --force --with keboola-agent-cli[server]
--prerelease=allow git+https://github.com/padak/keboola_agent_cli
uv resolves the git+ URL by checking out the DEFAULT BRANCH (main)
and reading pyproject.toml at HEAD. So even though _fetch_kbagent_
latest_prerelease() advertised "0.44.0b1", uv would silently install
whatever main carried at HEAD (e.g. 0.43.2) -- because the tag was
never consulted. The user sees "kbagent update --beta -> 0.44.0b1"
in the version banner and ends up with 0.43.2.
The fix tag-pins the install URL when (and only when) opting into
prerelease:
build_kbagent_upgrade_command(prerelease=True,
target_version="0.44.0b1")
now appends @v0.44.0b1 to the git+ URL, so uv installs the exact
commit the tag points to. _update_kbagent forwards the value it
already had from _fetch_kbagent_latest_version().
Stable upgrades intentionally pass target_version=None: main IS the
stable channel, so tag-pinning would just add a needless HTTP round-
trip without changing the resolved version. The default behaviour
(no --beta, no tag suffix) is byte-for-byte unchanged from 0.43.2.
Tests:
- test_uv_prerelease_with_target_version_appends_tag: pre-release
path emits @v<version> in install spec
- test_uv_stable_with_target_version_ignores_tag: target_version is
silently ignored when prerelease=False (no regression for stable)
Docs:
- CLAUDE.md "Beta / pre-release versions" section reworded: third
gate (tag-pinned install URL) added to the two-gate model, author
workflow renamed to "Releasing a beta from a feature branch"
reflecting the canonical flow (PR head, not main)
- changelog: 0.43.3 entry describes the bug + fix + new gate.
This release-pattern was discovered while preparing PR #310 for
beta release: the agent-cli-parity feature lives on a long-running
PR branch, main is on 0.43.2, and we want kbagent update --beta to
land users on the actual beta tag rather than silently fall back
to main.
padak
left a comment
There was a problem hiding this comment.
Review of #317 — feat(0.43.3): kbagent update --beta + tag-pinned install URL
Generated by
kbagent-pr-reviewersubagent. Verdict and findings below
are advisory; the human author retains every veto. CI-coverable issues
(lint, format, tests) are confirmed viamake check, not duplicated here.
Summary
This PR introduces opt-in pre-release support for kbagent update and kbagent version via a --beta flag and KBAGENT_INCLUDE_PRERELEASE=1 env var, plus a tag-pinned install URL fix ("Variant B") that prevents uv from silently installing stale main HEAD when a beta tag lives on a feature branch. The implementation is clean and well-tested at the service layer. Two NON-BLOCKING silent-drift gaps exist: plugins/kbagent/agents/keboola-expert.md VERSION GATE does not document the new --beta flag's minimum version, and the get_versions() JSON response's upgrade_command field does not reflect the beta install command when --beta is active. The AGENT_CONTEXT env-var listing section is also missing KBAGENT_INCLUDE_PRERELEASE. A NIT: the lazy import os inside _env_opted_into_prerelease is inconsistent with the module-level import os pattern used everywhere else.
Verdict
- Verdict: COMMENT
- Blocking findings: 0
- Non-blocking findings: 3
- Nits: 1
Blocking findings
(none)
Non-blocking findings
[NB-1] plugins/kbagent/agents/keboola-expert.md:62 — VERSION GATE missing kbagent update/version --beta minimum-version entry
The --beta flag is new in 0.42.0 (or 0.43.3 if using the tag-pinned Variant B fix). The VERSION GATE list in keboola-expert.md §1 Rule 6 does not include a line like "kbagent update --beta / kbagent version --beta opt-in needs 0.42.0+". An AI agent running on a 0.41.x install that is asked "install the latest beta" will run kbagent update --beta, get No such option: --beta, and fail without a useful fallback path.
Fix: add one line to the VERSION GATE list: `kbagent update --beta` / `kbagent version --beta` (opt into PEP 440 pre-releases; Variant B tag-pin needs 0.43.3+) need 0.42.0+,
[NB-2] src/keboola_agent_cli/services/version_service.py:638 — get_versions() returns stale upgrade_command when include_prerelease=True
When kbagent --json version --beta is run and a beta is the latest, kbagent.latest_version correctly returns e.g. "0.44.0b1", but kbagent.upgrade_command still returns the static uv tool install --upgrade git+... without --prerelease=allow or @v0.44.0b1. Any downstream consumer that reads upgrade_command from the JSON output will run the wrong install command and land on the stable version instead of the advertised beta.
Fix: pass prerelease=include_prerelease, target_version=kbagent_latest if include_prerelease else None to build_kbagent_upgrade_command() and set kbagent.upgrade_command = " ".join(cmd) in the return dict when cmd is not None, or at minimum annotate the field docstring so callers know it reflects stable-only.
[NB-3] src/keboola_agent_cli/commands/context.py:1023 — KBAGENT_INCLUDE_PRERELEASE missing from env-var listing in AGENT_CONTEXT
The env var is documented inline in the kbagent version and kbagent update description blocks (lines 959, 971) but is absent from the dedicated "Environment Variables" listing at lines 1023-1040. The kbagent context JSON output that AI agents read will not list it alongside KBAGENT_AUTO_UPDATE, KBAGENT_SKIP_UPDATE, etc. — an agent that scans the env section to understand all valid overrides will miss it.
Fix: add KBAGENT_INCLUDE_PRERELEASE Set to "1" (or "true/yes/on") to opt into pre-release versions for kbagent update / version in this shell (equivalent to --beta; never affects the auto-update startup hook). to the env-var block.
Nits
[NIT-1]src/keboola_agent_cli/commands/version.py:183—import osis lazy (inside the function body of_env_opted_into_prerelease) while every other file incommands/andauto_update.pyuses a module-levelimport os. Move it to the top of the file for consistency with the codebase pattern.
Verification log
gh pr view 317 --json title,body,files,additions,deletions,baseRefName,headRefName,labels,state— 12 files, +513/-36, titlefeat(0.43.3): kbagent update --beta + tag-pinned install URL, state OPEN, headfeat/update-beta-tag-aware-installbasemain✓git rev-parse --abbrev-ref HEAD(in worktree/Users/padak/github/keboola_agent_cli/.claude/worktrees/update-beta-fix) →feat/update-beta-tag-aware-install✓ matches PR head branch- Read
CONTRIBUTING.mdPlugin synchronization map ✓ - Read
plugins/kbagent/agents/keboola-expert.md§1 VERSION GATE, §2 Tool Selection Matrix, §3 Inline Gotchas ✓ - Layer violation check (typer in services, httpx in commands, formatter in clients):
grep→ empty ✓ no layer violations OPERATION_REGISTRYcheck for new commands:version→"read",update→"admin"already registered ✓ (no new commands, only new flags on existing commands)--hintexception check:updateandversionare listed in CONTRIBUTING.md §"Exception -- infrastructure-level commands" ✓ hint skipped intentionallymake check→3393 passed, 7 skipped, 97 deselected, 14 warnings in 62.82sexit 0 ✓commands/context.pyAGENT_CONTEXT:kbagent version [--beta]andkbagent update [--beta]with--beta (since 0.42.0)documentation present ✓CLAUDE.md## All CLI Commands:kbagent version [--beta]andkbagent update [--beta]entries present ✓plugins/kbagent/skills/kbagent/references/commands-reference.md: both entries updated withsince v0.42.0✓plugins/kbagent/agents/keboola-expert.mdVERSION GATE: noupdate --beta/version --betaentry → NB-1 findingplugins/kbagent/skills/kbagent/references/gotchas.md: nokbagent update --beta/KBAGENT_INCLUDE_PRERELEASEentry; the behavior is documented inline incommands-reference.mdandCLAUDE.mdbut not tagged(since vX.Y.Z)ingotchas.md. The flag is discoverable from--helpso this is low-risk; filed as open question below rather than a finding.server/routers/health.py:36GET /versioncallsget_versions()withoutinclude_prerelease— acceptable as a write/install command is infra-only, butGET /version --betaflavor is also infra. No REST skip documented in PR description → informational (infrastructure command category, not data-plane)- Backward compatibility:
get_versions()shape unchanged for stable callers (newinclude_prereleaseparam defaults to False) ✓;self_update()same ✓;build_kbagent_upgrade_command()new keyword-only params with defaults ✓ _fetch_kbagent_latest_prereleaseerror handling:response.raise_for_status()fires inside_fetch_kbagent_latest_version's outertry/except (httpx.HTTPError, KeyError, ValueError)✓auto_update.pystartup hook: calls_fetch_kbagent_latest_version()withoutinclude_prerelease(stable channel only) ✓ startup hook never silently lands on beta- Convention checks (magic numbers, raw error_code strings, bare except, print()): all clean ✓
- CLI-layer CliRunner tests for
--betaonversion/updatecommands: absent (12 new service-layer tests confirmed, no CliRunner tests for the new Typer option) → noted, not blocking for infrastructure commands per project pattern
Open questions for the author
-
gotchas.md— Is the "no gotcha entry forkbagent update --beta" deliberate? The flag is documented incommands-reference.md(since v0.42.0) but there is no(since v0.42.0)entry ingotchas.mdcovering the behavior that--betadoes NOT affect the startup auto-update hook. This is arguably the most confusing behavior (user opts in, uninstalls, re-installs, and is surprised that auto-update put them back on stable). Worth a gotcha entry. -
server/routers/health.py:38— ShouldGET /versionaccept a?include_prerelease=truequery param to mirror the CLI--betaflag? If not, documenting the explicit skip in the PR description would satisfy CONTRIBUTING.md's "document skip" requirement for external-consumer parity.
Three non-blocking findings from kbagent-pr-reviewer review of PR #317: NB-1 (keboola-expert.md VERSION GATE): Rule 6 listed every prior version-gated capability but had no entry for `kbagent update --beta` / `version --beta`. An AI agent running on a 0.41.x install asked to "install the latest beta" would have no warning that --beta requires 0.43.3+. Added compact entry `\`kbagent update --beta\` = 0.43.3+` to the gate list. Trimmed two neighbouring entries (#304, #312) by removing redundant parenthesis spaces to stay under the 60000-byte prompt budget -- the system prompt was already at 99.8% capacity before this fix. NB-2 (get_versions() upgrade_command): `kbagent --json version --beta` correctly reported the prerelease in `kbagent.latest_version` but the `kbagent.upgrade_command` field was a static `uv tool install --upgrade git+...` string with no `--prerelease=allow` and no `@v<version>` tag-pin. Downstream consumers reading the JSON would copy this command, run it, and silently land on the stable channel even though the field said "0.44.0b1 is available". get_versions() now calls `build_kbagent_upgrade_command(prerelease=include_prerelease, target_version=...)` and joins the result -- the rendered command now matches what self_update() actually runs. NB-3 (commands/context.py AGENT_CONTEXT env-var block): KBAGENT_INCLUDE_PRERELEASE was documented inline next to the `version`/`update` commands but missing from the dedicated "Environment Variables" listing at §7. AI agents that scan that block to discover all overrides would miss it. Added entry next to KBAGENT_MCP_TRANSPORT with the same shape as the inline help. Test plan: - `make check` -> 3393 passed (was 3393 + 1 budget overflow before trim; now under 60000-byte ceiling at exactly 60000 bytes after the two-character trim on neighbouring entries) - get_versions(include_prerelease=False) zero behaviour change -> upgrade_command identical to prior release for stable callers.
Summary
kbagent update --beta(andKBAGENT_INCLUDE_PRERELEASE=1) opt-in for pre-release channel — discovery hits/releases(plural) and picks highest PEP 440, install propagates--prerelease=allow(uv) /--pre(pip)@v<version>when (and only when) opting into prerelease, so beta tags living on a feature branch resolve correctly instead of silently falling back to main HEADWhy three commits
feat(0.42.0)cherry-picked from PR feat(0.44.0): kbagent agent <verb> -- CLI parity for /agents REST surface #310 — originalkbagent update --betaimplementation. PEP 440 + GitHub--prereleasegates.docs:cherry-picked from PR feat(0.44.0): kbagent agent <verb> -- CLI parity for /agents REST surface #310 — CLAUDE.md and CONTRIBUTING.md beta workflow docs.fix(0.43.3)new — the Variant B fix that makes--betaactually work when betas live on feature branches.The original v0.42.0 implementation in PR #310 had a latent bug:
uv tool install --prerelease=allow git+URLresolves the default branch (main), so even though_fetch_kbagent_latest_prerelease()advertised0.44.0b1from a feature-branch tag, uv would silently install whatever main carried at HEAD. The fix appends@v<version>to the install URL.This release-pattern was discovered while preparing PR #310 for beta release: the agent-cli-parity feature lives on a long-running PR branch, main is on 0.43.2, and we want
kbagent update --betato land users on the actual beta tag rather than silently fall back to main.Test plan
test_version_service.py(PEP 440 sort, draft skip, fallback to stable, invalid tag handling, HTTP failure, uv prerelease, pip prerelease, NEW: prerelease+target_version appends @v, NEW: stable+target_version ignores tag)make checkpasses locally (3393 tests green)kbagent update(stable path) from a 0.43.x install → resolves to 0.43.3, no tag-pinning regression0.44.0b1from the feature branchFollow-up
After this lands as 0.43.3, PR #310 will be rebased onto 0.43.3, will drop the duplicated
--betaand Variant B commits (already in main), bump to0.44.0b1, and ship as a tagged beta release. Beta users will then be able to opt in viakbagent update --betaand land on the actual 0.44.0b1 tag.