Skip to content

fix(data-app): read setup.sh code, not its comments, in validate-repo - #636

Merged
padak merged 1 commit into
mainfrom
fix/validate-repo-comment-stripping
Aug 22, 2026
Merged

fix(data-app): read setup.sh code, not its comments, in validate-repo#636
padak merged 1 commit into
mainfrom
fix/validate-repo-comment-stripping

Conversation

@padak

@padak padak commented Aug 22, 2026

Copy link
Copy Markdown
Member

The bug

kbagent data-app validate-repo checks that keboola-config/setup.sh does not use the pip installer (the data-app runtime blocks it; uv sync is the supported path). It enforces that by grepping the raw file:

_PIP_INSTALL_RE = re.compile(r"\bpip\s+install\b")
...
if _PIP_INSTALL_RE.search(snapshot.setup_sh):   # -> SEVERITY_BLOCKING

Comments are part of the raw file, so this setup.sh is rejected:

#!/bin/bash
# always uv sync here, never pip install
uv sync

It does the right thing and says so, and the saying is what blocks it. The rule penalises precisely the author who documents it for their colleagues.

The same misreading runs the other way for the companion rule. golden-rule.setup-sh-uv-sync warns when pyproject declares dependencies but setup.sh does not invoke uv sync — and a comment merely mentioning uv sync satisfied it. A script that installed nothing could pass.

Worth flagging

The module comment above these regexes justifies the heuristics like this:

Heuristic regexes -- tightening any of these is preferred over a real parser because [...] false positives are operationally cheap (a WARN, not a BLOCKING).

That holds for the other regexes there. It does not hold for this one: setup-sh-no-pip emits SEVERITY_BLOCKING. The stated rationale for tolerating imprecision never covered the one check where imprecision is expensive.

The fix

Both rules ask what the script runs, so both now read comment-stripped code.

_strip_shell_comments is deliberately not a shell parser. It tracks single/double quotes so a # inside a string survives, and treats a # at line start or after whitespace as opening a comment. That covers real setup.sh files without pretending to handle heredocs or ${...#...} expansions — consistent with the heuristic-over-parser choice the module already made.

Tests

Four added. Two of them target the bug and fail on main:

  • test_pip_install_only_in_a_comment_does_not_block — was BLOCKING, now OK
  • test_uv_sync_only_in_a_comment_still_warns — was OK, now WARN

Two are regression guards against over-stripping, and pass either way:

  • test_pip_install_with_a_trailing_comment_still_blocks — a real invocation with a trailing comment must not slip through
  • test_hash_inside_a_quoted_string_is_not_a_comment — quoted # must not truncate the line

Full suite: 5832 passed, 61 skipped. ruff check, ruff format --check, ty check clean.

Notes

Found while building a sample data app for the E2E fixtures — the repo was rejected over its own comment. No version bump or changelog entry; the version is being bumped once across several PRs.

Related but not addressed here: validate-repo never inspects the nginx listen port, which is a hardcoded platform contract (8888). A repo can report 0 BLOCKING and still fail to start. Happy to open that separately.


Open in Devin Review

`validate-repo` greps keboola-config/setup.sh for `pip install` and blocks
the repo when it matches. It greps the raw file, so a COMMENT warning
against the wrong installer -- exactly what a careful author writes above
the right one -- is read as the violation it warns against:

    #!/bin/bash
    # always uv sync here, never pip install
    uv sync

That repo reports BLOCKING while being entirely correct. The rule penalises
the author who documents it.

The same reading runs the other way for the companion rule: a comment merely
mentioning `uv sync` satisfied the check that the script actually invokes it,
so a script that installs nothing could pass.

Both rules ask what the script RUNS, so both now read comment-stripped code.

`_strip_shell_comments` is deliberately not a shell parser -- it tracks
single/double quotes so a `#` inside a string survives, and treats a `#` at
line start or after whitespace as opening a comment. That covers real
setup.sh files without pretending to handle heredocs or ${...#...}.

Worth noting the surrounding comment in the module already justifies these
heuristics on the grounds that "false positives are operationally cheap (a
WARN, not a BLOCKING)" -- which is true of the other regexes there, but this
one emits BLOCKING, so that justification never covered it.

Found while building a sample data app: the repo was rejected over its own
comment.

@devin-ai-integration devin-ai-integration 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@padak
padak merged commit 98e0fd5 into main Aug 22, 2026
5 checks passed
@padak
padak deleted the fix/validate-repo-comment-stripping branch August 22, 2026 13:47
padak added a commit that referenced this pull request Aug 22, 2026
…rade to

`upgrade_command` in `kbagent version --json` is documented as a string a
consumer may shell out to verbatim, but `get_kbagent_version_info()` built
it unconditionally. Both non-False states of `up_to_date` therefore handed
out an actively wrong command:

* `true` (local >= latest). A caller on a pre-release compares ahead of the
  stable release a non-`--beta` fetch returns (`0.44.0b1` >= `0.43.3`), so it
  read `up_to_date: true` beside a `--force --reinstall` command pinned to the
  OLDER stable wheel. Running it is a silent downgrade off the beta -- the
  exact foot-gun the beta channel's three gates exist to prevent.
* `null` (release feed unreachable). `resolve_kbagent_wheel_url(None)` yields
  no asset, so it fell through to the unpinned `git+` source install, resolving
  whatever the default branch happens to be -- on the one code path reached
  precisely because kbagent could not establish what the current release is.

`prepare_kbagent_update_plan()` already gated on `up_to_date is False`, so
`kbagent update` itself was never affected; only the reported string was. The
frozen (PyInstaller) channel branch is untouched -- `brew upgrade` and friends
are no-ops when current, not downgrades, and `install_channel` consumers rely
on that shape.

Two tests added; both fail on the parent commit.

Also in this commit, for the 0.88.0 release span:

- changelog: `(#issue)` decorations on all 24 bullets. The `SDK:` prefix is not
  in `_PREFIX_RE`, so that bullet rendered uncoloured -- it becomes `New (#622):`
  with the SDK context kept in the sentence.
- changelog: entries for #636 and #637, which carried no version bump and so
  arrived with no changelog at all.
- four first sentences shortened under the 160-char headline budget that
  `test_changelog_render.py` enforces -- two of them were pushed over by the
  decorations added here.
padak added a commit that referenced this pull request Aug 22, 2026
…rade to

`upgrade_command` in `kbagent version --json` is documented as a string a
consumer may shell out to verbatim, but `get_kbagent_version_info()` built
it unconditionally. Both non-False states of `up_to_date` therefore handed
out an actively wrong command:

* `true` (local >= latest). A caller on a pre-release compares ahead of the
  stable release a non-`--beta` fetch returns (`0.44.0b1` >= `0.43.3`), so it
  read `up_to_date: true` beside a `--force --reinstall` command pinned to the
  OLDER stable wheel. Running it is a silent downgrade off the beta -- the
  exact foot-gun the beta channel's three gates exist to prevent.
* `null` (release feed unreachable). `resolve_kbagent_wheel_url(None)` yields
  no asset, so it fell through to the unpinned `git+` source install, resolving
  whatever the default branch happens to be -- on the one code path reached
  precisely because kbagent could not establish what the current release is.

`prepare_kbagent_update_plan()` already gated on `up_to_date is False`, so
`kbagent update` itself was never affected; only the reported string was. The
frozen (PyInstaller) channel branch is untouched -- `brew upgrade` and friends
are no-ops when current, not downgrades, and `install_channel` consumers rely
on that shape.

Two tests added; both fail on the parent commit.

Also in this commit, for the 0.88.0 release span:

- changelog: `(#issue)` decorations on all 24 bullets. The `SDK:` prefix is not
  in `_PREFIX_RE`, so that bullet rendered uncoloured -- it becomes `New (#622):`
  with the SDK context kept in the sentence.
- changelog: entries for #636 and #637, which carried no version bump and so
  arrived with no changelog at all.
- four first sentences shortened under the 160-char headline budget that
  `test_changelog_render.py` enforces -- two of them were pushed over by the
  decorations added here.
padak added a commit that referenced this pull request Aug 22, 2026
…rade to

`upgrade_command` in `kbagent version --json` is documented as a string a
consumer may shell out to verbatim, but `get_kbagent_version_info()` built
it unconditionally. Both non-False states of `up_to_date` therefore handed
out an actively wrong command:

* `true` (local >= latest). A caller on a pre-release compares ahead of the
  stable release a non-`--beta` fetch returns (`0.44.0b1` >= `0.43.3`), so it
  read `up_to_date: true` beside a `--force --reinstall` command pinned to the
  OLDER stable wheel. Running it is a silent downgrade off the beta -- the
  exact foot-gun the beta channel's three gates exist to prevent.
* `null` (release feed unreachable). `resolve_kbagent_wheel_url(None)` yields
  no asset, so it fell through to the unpinned `git+` source install, resolving
  whatever the default branch happens to be -- on the one code path reached
  precisely because kbagent could not establish what the current release is.

`prepare_kbagent_update_plan()` already gated on `up_to_date is False`, so
`kbagent update` itself was never affected; only the reported string was. The
frozen (PyInstaller) channel branch is untouched -- `brew upgrade` and friends
are no-ops when current, not downgrades, and `install_channel` consumers rely
on that shape.

Two tests added; both fail on the parent commit.

Also in this commit, for the 0.88.0 release span:

- changelog: `(#issue)` decorations on all 24 bullets. The `SDK:` prefix is not
  in `_PREFIX_RE`, so that bullet rendered uncoloured -- it becomes `New (#622):`
  with the SDK context kept in the sentence.
- changelog: entries for #636 and #637, which carried no version bump and so
  arrived with no changelog at all.
- four first sentences shortened under the 160-char headline budget that
  `test_changelog_render.py` enforces -- two of them were pushed over by the
  decorations added here.
padak added a commit that referenced this pull request Aug 22, 2026
…eport fix (#638)

Nothing between v0.87.0 and today had been published: #629 bumped pyproject to
0.88.0, #633 bumped it again to 0.89.0, and both changelog blocks sat unreleased.
Ship the whole span as one release instead of publishing two versions
retroactively.

- changelog: merge the 0.89.0 bullets into 0.88.0, reordered so the #624
  column-description fix leads -- the first bullet is what `kbagent changelog`
  renders as the one-line summary. Add (#issue) decorations to all 24 bullets;
  the `SDK:` prefix is not in \_PREFIX_RE so that bullet rendered uncoloured and
  becomes `New (#622):`. Add entries for #636 and #637, which carried no version
  bump and so arrived with no changelog at all.
- pyproject 0.89.0 -> 0.88.0, propagated to plugin.json, marketplace.json and
  uv.lock via `make version-sync`.
- rewrite every `0.89.0+` / `(since v0.89.0)` version gate across CLAUDE.md, the
  keboola-expert prompt, commands-reference.md, gotchas.md, context.py,
  docs/sdk.md and two test comments. A stale gate makes the agent refuse flags
  that do exist on the user's installed version.
- fix: `kbagent version --json` no longer advertises an `upgrade_command` when
  there is nothing to upgrade to. It was built unconditionally, so a caller on a
  pre-release read `up_to_date: true` beside a `--force --reinstall` command
  pinned to the OLDER stable wheel (a silent downgrade), and an unreachable
  release feed produced an unpinned `git+` default-branch install. `kbagent
  update` itself was never affected. Two tests added; both fail on the parent.
padak added a commit that referenced this pull request Aug 22, 2026
Audited every 0.88.0 changelog bullet against the agent-facing reference
surfaces (commands-reference.md, gotchas.md, the *-workflow.md set,
keboola-expert.md). Everything was covered except one change, plus two
entries that were accurate but incomplete.

- gotchas: `data-app validate-repo`'s two setup.sh rules now read
  comment-stripped code (#636). This had NO coverage anywhere. It matters to
  an agent because it broke in both directions before 0.88.0: a script whose
  comment read `# never pip install` was rejected as BLOCKING, and a script
  whose only `uv sync` was in a comment satisfied the dependency-install rule
  while installing nothing. An agent advising on a repo validated by an older
  kbagent needs to know a BLOCKING may be about a comment. The stripper's
  limits (not a shell parser; heredocs out of scope) are stated too, so nobody
  reads it as a guarantee.
- commands-reference: the same note on the `validate-repo` entry.
- commands-reference: the `table-detail` entry documented #621's `definition`
  thoroughly but said nothing about #624's READ side -- the resolution
  precedence into `column_details[].description` and the
  `legacy_column_descriptions` field. The `describe-column` entry pointed at
  table-detail for read-back; table-detail did not advertise it.
- data-app-workflow: the same comment-stripping note in the pre-flight
  validation section.

No behaviour change, docs only. `check_version_gates.py` now resolves 419
markers (up from 418).
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