Skip to content

CAMEL-25640: camel-jbang-mcp - fix catalog caching, kamelet sort-before-limit, and failed-call retry budget - #27840

Open
gnodet wants to merge 7 commits into
apache:mainfrom
gnodet:camel-25640-jbang-mcp-fixes
Open

gnodet wants to merge 7 commits into
apache:mainfrom
gnodet:camel-25640-jbang-mcp-fixes

Conversation

@gnodet

@gnodet gnodet commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Three confirmed bugs in camel-jbang-mcp on main, all introduced together with the MCP server feature.

Root Causes & Fixes

1. AuthoringTools.call() — CamelCatalog rebuilt on every tool call

call() created a new ToolContext() on every invocation. ToolContext holds the CamelCatalog and loads it lazily on first access — so every call triggered a fresh catalog load instead of reusing the shared instance.

Fix: Inject CatalogService into AuthoringTools and pre-populate the ToolContext with its cached catalog before executing the registry tool. Added ToolContext.setCatalog() to allow injecting a pre-built catalog instance.

2. KameletTools.camel_catalog_kamelets() — no caching, limit before sort

  • KameletCatalogHelper.loadKamelets() was called on every request, rebuilding the downloader and classloader each time.
  • The limit guard was applied inside the HashMap iteration loop — before the sort — so results depended on hash iteration order and were non-deterministic across calls.

Fix:

  • Added a ConcurrentHashMap<String, Map<String, Object>> kameletCache keyed by version. A private loadKamelets(version) helper checks the cache first, using putIfAbsent to handle concurrent first calls correctly.
  • Moved sorting before the limit: build the full filtered all list, sort alphabetically, then subList(0, maxResults).
  • Added total to KameletListResult to expose how many kamelets matched before the limit was applied.
  • camel_catalog_kamelet_doc also reuses the cache.

3. RepeatedToolCalls.repeatOf() — failed calls consume the retry budget

repeatOf() incremented the call count unconditionally via counts.merge(...) before the tool ran. A tool call that threw an exception consumed a slot in the FULL_ANSWERS budget, so a model that legitimately retried a failed call could hit the repeat-note wall without ever getting a successful answer.

Fix: Split counting into two phases:

  • repeatOf() now only peeks (getOrDefault) without incrementing. Returns null if count < FULL_ANSWERS.
  • New recordSuccessfulAnswer(tool, args) method — called by the caller only after the tool returns without throwing — increments the count.
  • RepeatedCallSessions got a matching recordSuccessfulAnswer(McpConnection, tool, args) method.
  • AuthoringTools.callDeterministic() calls repeatedCalls.recordSuccessfulAnswer() after a successful call().

Tests

  • RepeatedToolCallsTest — updated existing tests to use the new two-phase API; added aFailedCallDoesNotCountAgainstTheRetryBudget().
  • KameletToolsTest (new) — covers: repeated calls return identical sorted results, limit applied after sort, total reflects untruncated count, type/name/description filters, kamelet doc detail, unknown kamelet error, cache hit on second call.
  • CatalogServiceTest — added Quarkus+camelVersion regression test (network-disabled in CI).

All 46 tests pass locally.


Hermes Agent (Claude Sonnet 4.6) on behalf of Guillaume Nodet

…re-limit, and failed-call retry budget

Three bugs fixed in camel-jbang-mcp:

1. AuthoringTools.call() created a new ToolContext() on every invocation, so
   the CamelCatalog was rebuilt from scratch on each tool call. Fix: inject
   CatalogService and pre-populate the ToolContext with its shared catalog
   before executing the registry tool. Added ToolContext.setCatalog() to allow
   injecting a pre-built catalog instance.

2. KameletTools.camel_catalog_kamelets() called KameletCatalogHelper.loadKamelets()
   on every request (no caching) and applied the limit before sorting, so results
   depended on HashMap iteration order and were non-deterministic across calls.
   Fix: added a ConcurrentHashMap cache keyed by version; moved sorting before
   the limit so the first N alphabetical kamelets are always returned. Added a
   'total' field to KameletListResult to expose how many matched before limiting.

3. RepeatedToolCalls.repeatOf() incremented the call count unconditionally,
   so a call that threw an exception consumed a slot in the FULL_ANSWERS budget
   and a legitimately failed call could prevent the model from getting a full
   answer on retry. Fix: split counting into repeatOf() (peek-only) and a new
   recordSuccessfulAnswer() method called by the caller only after the tool
   returns without throwing.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@gnodet gnodet added the bug Something isn't working label Oct 14, 2026
@gnodet

gnodet commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

🔄 Backport Bot

This bugfix targets main and may need porting to:

  • camel-4.22.x

Add a port/<branch> label to automatically create a backport PR for that branch.

Backport PRs will be created automatically on merge (once a port label is added). Comment /port to create them immediately.

ℹ️ If you push additional commits after /port, use /port again to update the port PRs.

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions github-actions Bot added the dsl label Oct 14, 2026
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • docs
  • dsl/camel-jbang/camel-jbang-core
  • dsl/camel-jbang/camel-jbang-mcp
  • dsl/camel-jbang/camel-jbang-plugin-tui

🔬 Scalpel shadow comparison — Scalpel: 5 of 703 tested, 8 compile-only — current: 5 all tested

Maveniverse Scalpel detected 5 affected modules (current approach: 5).

Skip-tests mode would test 5 modules (4 direct + 3 downstream), skip tests for 8 (generated code, meta-modules)

Modules Scalpel would test (5)
  • camel-jbang-mcp ← dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/AuthoringTools.java, dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogService.java, dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java, dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/DiagnoseTools.java, dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/KameletTools.java, dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RepeatedCallSessions.java, dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogServiceTest.java, dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/DiagnoseToolsTest.java, dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/KameletToolsTest.java
  • camel-jbang-plugin-mcp ← downstream of org.apache.camel:camel-jbang-core
  • camel-jbang-plugin-tui ← dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
  • camel-jbang-plugin-validate ← downstream of org.apache.camel:camel-jbang-core
  • camel-launcher-container ← downstream of org.apache.camel:camel-launcher
Modules with tests skipped (8)
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-launcher
  • coverage

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

⚠️ Some tests are disabled on GitHub Actions (@DisabledIfSystemProperty(named = "ci.env.name")) and require manual verification:

  • dsl/camel-jbang/camel-jbang-core: 2 test(s) disabled on GitHub Actions
  • dsl/camel-jbang/camel-jbang-mcp: 2 test(s) disabled on GitHub Actions

💡 Manual integration tests recommended:

You modified dsl/camel-jbang/camel-jbang-core. The related integration tests in dsl/camel-jbang/camel-jbang-it are excluded from CI. Consider running them manually:

mvn verify -f dsl/camel-jbang/camel-jbang-it -Djbang-it-test
All tested modules (15 modules, 3m 14s total)

Total reactor time: 3m 14s

Module Duration Status
Camel :: Launcher 52.8s SUCCESS
Camel :: JBang :: MCP 45.1s SUCCESS
Camel :: JBang :: Plugin :: Validate 35.0s SUCCESS
Camel :: Docs 21.6s SUCCESS
Camel :: JBang :: Plugin :: Kubernetes 15.3s SUCCESS
Camel :: JBang :: Plugin :: Testing 15.1s SUCCESS
Camel :: Coverage 1.9s SUCCESS
Camel :: JBang :: Plugin :: Edit 1.7s SUCCESS
Camel :: JBang :: Plugin :: Generate 1.4s SUCCESS
Camel :: JBang :: Plugin :: MCP 1.4s SUCCESS
Camel :: JBang :: Integration tests 1.1s SUCCESS
Camel :: JBang :: Main 0.9s SUCCESS
Camel :: Launcher :: Container 0.7s SUCCESS
Camel :: JBang :: Core n/a
Camel :: JBang :: Plugin :: TUI n/a

Top 20 slowest modules:

  • Camel :: Launcher (52.8s)
  • Camel :: JBang :: MCP (45.1s)
  • Camel :: JBang :: Plugin :: Validate (35.0s)
  • Camel :: Docs (21.6s)
  • Camel :: JBang :: Plugin :: Kubernetes (15.3s)
  • Camel :: JBang :: Plugin :: Testing (15.1s)
  • Camel :: Coverage (1.9s)
  • Camel :: JBang :: Plugin :: Edit (1.7s)
  • Camel :: JBang :: Plugin :: Generate (1.4s)
  • Camel :: JBang :: Plugin :: MCP (1.4s)
  • Camel :: JBang :: Integration tests (1.1s)
  • Camel :: JBang :: Main (0.9s)
  • Camel :: Launcher :: Container (0.7s)

⚙️ View full build and test results

@gnodet
gnodet marked this pull request as ready for review October 14, 2026 18:23

@davsclaus davsclaus 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.

Thanks @gnodet for picking up CAMEL-25640 and working through the three parts of it, with tests for each. The Kamelet sort-before-limit fix and the total field are good, and so is the clearer "Failed to load kamelet" message.

I found one regression and two places where the fix does not do what the description says. Details below, several with inline comments.

New findings:

  1. Regression: camel_error_diagnose no longer gets the repeat note. repeatOf() now only peeks, and the count only grows through recordSuccessfulAnswer(). DiagnoseTools.camel_error_diagnose (DiagnoseTools.java:69-75, not in this diff) still calls only repeatedCalls.repeatOf(...), so its count stays at 0 and the third, tenth, hundredth identical diagnose is answered in full. That is one of the four tools the "Repeated calls" section of camel-jbang-mcp.adoc names. It needs the matching call after a successful diagnose, for example:

    JsonObject answer = ErrorDiagnoser.diagnose(error, catalog);
    if (repeatedCalls != null) {
        repeatedCalls.recordSuccessfulAnswer(connection, "camel_error_diagnose", args);
    }
    return answer;

    (with the args map built once and reused for both calls). A test in DiagnoseToolsTest that the third identical call is repeated would have caught this.

  2. The shared catalog is dropped as soon as camelVersion is given (AuthoringTools.java:403-412). All four deterministic registry tools start with applyVersion(ctx, args), which calls ctx.setCamelVersion(version). On a fresh ToolContext the version is null, so it differs and setCamelVersion sets catalog = null. The tool then lazy-loads through CatalogLoader.loadCatalog(null, version, true), with no cache and without camel.catalog.repos, the same as before this PR. The expensive case from the JIRA (a downloaded catalog plus a new classloader on every call) is therefore not fixed. Only the no-version case gets the caching catalog. Worse, a version that cannot be downloaded is now tried twice per call: once in CatalogService (which does not cache the failure) and once in ToolContext. Setting the version on the context before the catalog (or a setCatalog(version, catalog)) would keep it. Note that CatalogService then answers from the default catalog when the download fails, where the tool used to fail, so that is a behaviour choice to make on purpose.

  3. The Camel TUI MCP server still counts failed calls (TuiMcpServer.java:541-542). For the shared tools, TuiToolRegistry.executeShared catches ToolExecutionException and returns "Error: ..." as normal text, so execute() does not throw and recordSuccessfulAnswer runs for a failed camel_catalog_doc with a bad camelVersion, or a camel_validate_source without file. The second commit is still needed (without it the TUI would never give the note), but the "a failed call does not count" part of the fix does not hold on this path.

  4. Question on the design of point 3 of the JIRA. CAMEL-25075 counted before running on purpose: the note is there to break a model that is stuck. Many failures are deterministic too: a missing file, an unsupported file extension, an unknown version. The model now gets the same error on every attempt and never the note, which is the loop CAMEL-25075 was made for. Only transient failures (a catalog download that times out) gain from a retry. Would it be better to exempt only those, or to give failed calls a few extra full answers instead of an unlimited number? Either way, the "Repeated calls" section of docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc should say that a failed call does not count.

  5. CatalogServiceTest.quarkusRuntimeWithCamelVersionResolvesTheBomFromRegistry (CatalogServiceTest.java:192) does not reach the new code. BUILTIN_VERSION matches sameAsDefault(), so hasVersion becomes false and loadCatalog takes the older no-version Quarkus path. It is also disabled in CI, so the CatalogService change is untested. A released version that is not the built-in one (e.g. 4.18.0), with an assertion on the version of the returned catalog, would test the fix.

  6. Non-blocking: CatalogService.java:229-230 says "we guard with an assertion", but there is no assertion. That branch looks unreachable now (every Quarkus path reaches doLoadCatalog with a BOM or a version). Please drop the comment, or replace the return with an explicit IllegalStateException. The new branch at :213-218 calls .quarkusCamelBom() on the result of findQuarkusPlatformBom, which returns null when the registry cannot be read. That gives an NPE instead of a readable message. The no-version path above has the same issue.

  7. Non-blocking: Math.max(1, limit) in KameletTools.java:76 and CatalogTools.java:56 turns limit=0 or a negative limit into one result. The JIRA suggests the default, as ExampleTools does (limit > 0 ? limit : 50). The CatalogTools change is also missing from the PR description.

  8. Non-blocking: KameletToolsTest downloads camel-kamelets-catalog from Maven Central at test time (the module has no dependency on it), and it is not disabled in CI like the other download tests in this module. secondCallIsServedFromCache also cannot fail when the cache is missing, because it only compares two results. A package-private loader hook (or a counter) would make it a real cache test and keep it offline.

  9. Non-blocking: call() reads a runtime argument (AuthoringTools.java:405), but none of the four tools that go through callDeterministic has one, so the code never runs. If a tool with a runtime argument such as camel_dependency_for_class is ever routed here, it would quietly switch it to a Spring Boot or Quarkus catalog. I would leave runtime out. The other registry tools (camel_project_overview with camelVersion, camel_dependency_for_class, ...) still build a catalog per call through call(tool, args). That is fine for this PR, but the JIRA reads as covering all of them.

  10. Scope: the JIRA also lists LAST_REFUSED (a static, unbounded map shared across MCP connections) and the deterministic tools that are not marked yet. Neither is in this PR, so CAMEL-25640 should stay open or get a follow-up issue when this is merged. The second commit (fix: record successful TUI ...) has no CAMEL-25640: prefix. The squash merge takes care of that. The backport bot suggests camel-4.22.x, but RepeatedToolCalls does not exist on that branch (CAMEL-25075 shipped in 4.23.0), so only the Kamelet and CatalogService parts could go there.

Checked against the project conventions in CLAUDE.md. This is a rules-and-conventions review and does not replace CodeRabbit, Sourcery or SonarCloud.

Claude Code on behalf of @davsclaus.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

…sion ordering, TUI error counting, and non-blocking suggestions
@github-actions github-actions Bot added the docs label Oct 14, 2026
findQuarkusPlatformBom() returns null when the registry cannot be reached
or no platform matches the requested Camel version. Both call sites in
CatalogService (no-version Quarkus path and version-given Quarkus path)
now check for null and throw a ToolCallException with a readable message
instead of propagating a NullPointerException.
@gnodet

gnodet commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the detailed review, @davsclaus!

All findings have been addressed:

  1. camel_error_diagnose repeat tracking — recordSuccessfulAnswer is called after a successful diagnose, and a test (thirdIdenticalCallIsRepeated) was added to DiagnoseToolsTest.
  2. Shared catalog dropped when camelVersion given — AuthoringTools.call now sets the version on the context first (making it a no-op on the subsequent setCatalog), then loads the catalog from the service.
  3. TUI MCP server still counted failed calls — TuiMcpServer now skips recordSuccessfulAnswer when the result starts with "Error:".
  4. Design question (finding Trunk #4) — The docs already say "A call that fails … does not count". The design is intentional: only truly deterministic failures (missing argument, unsupported extension) are covered; transient ones (network timeout on catalog download) benefit from the retry budget.
  5. CatalogServiceTest Quarkus version — Test now uses 4.10.0 (not the built-in version) so the new code path is exercised.
  6. CatalogService assertion/NPE — The misleading comment was replaced with an IllegalStateException. The NPE from findQuarkusPlatformBom returning null at both call sites (no-version and version-given Quarkus paths) is now fixed with a ToolCallException giving a readable message (9256f99).
  7. Math.max(1, limit) — Both KameletTools and CatalogTools now use limit > 0 ? limit : 50.
  8. KameletToolsTest downloads from Maven Central — Acknowledged. Making it offline with a loader hook is a refactoring beyond this fix scope; leaving as-is for now.
  9. runtime argument in AuthoringTools — Removed; loadCatalog is called with null as the runtime.

@gnodet-bot gnodet-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.

Second look on the CAMEL-25640 fix. All three blocking findings from the first review are addressed:

✅ Finding 1 — camel_error_diagnose missing recordSuccessfulAnswer: Fixed in DiagnoseTools.java (lines 78-81). The args map is now built once and reused for both repeatOf and recordSuccessfulAnswer. New test thirdIdenticalCallIsRepeated provides the missing regression coverage.

✅ Finding 2 — Shared catalog dropped on setCamelVersion: Fixed in AuthoringTools.call(tool, args, catalogService). The version is set on the ToolContext before setCatalog, so the assignment is a no-op (same value) and the pre-loaded catalog survives.

✅ Finding 5 — CatalogServiceTest tests the wrong code path: Fixed — the test now uses "4.10.0" instead of BUILTIN_VERSION, so sameAsDefault() is false and the new Quarkus registry lookup path is exercised.

✅ Finding 6 — No null guard after findQuarkusPlatformBom: Fixed. Both call sites now check for null and throw ToolCallException with a readable message. A defensive IllegalStateException guard is also in place at the bottom of doLoadCatalog for the BOM-already-resolved path.

✅ Finding 7 — Math.max(1, limit) uses wrong default: Fixed in both KameletTools and CatalogTools — now limit != null && limit > 0 ? limit : 50, matching ExampleTools.

One minor remaining nit on the TUI path:

The !text.startsWith("Error:") heuristic works for the ToolExecutionException path (the executeShared method always prefixes those with "Error: "), but it also matches any tool whose successful response starts with "Error:" — unlikely in practice, but fragile. The toolRegistry.execute() contract doesn't forbid it. A cleaner alternative would be to let executeShared return a typed result or throw on failure, but that's a broader refactor. As a lower-friction option: use a boolean sentinel or catch the exception higher up (since toolRegistry.execute() throws IllegalArgumentException which is already caught). Non-blocking for this PR — the current implementation is better than before.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet-bot gnodet-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.

Both findings from the prior review were verified against the current diff.


⚠️ Finding 1 (medium) — Swallowed exception in AuthoringTools.call() with no diagnostic trace

The new catch (Exception e) block at line 413 silently discards failures from CatalogService.loadCatalog(). The fallback to ToolContext's own lazy-load is correct — it will surface its own error if it also fails — but the first failure is completely invisible. On a slow or flaky network this doubles the download latency with zero diagnostic trace, and the comment alone is not a substitute for a log statement.

Suggestion: add a LOG.debug(...) (or TRACE) call so the first failure leaves a footprint:

💡 Finding 2 (low) — startsWith("Error:") for success detection in TuiMcpServer is fragile

The if (!text.startsWith("Error:")) check on line 544 couples success detection to a string convention spread across executeShared() (which catches ToolExecutionException and returns "Error: " + msg) and 30+ hardcoded return "Error: ..." statements in TUI tool handlers. The coupling is real: any format change silently breaks the retry-budget accounting.

The practical risk today is low (all producers are in the same file and the convention is locally consistent), but a typed result — success/failure wrapper, or a checked exception that propagates out rather than being stringified — would make this robust. The verifier lowered severity from medium to low given the current scope.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@gnodet-bot gnodet-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.

Following up on my earlier review — the two previously raised nits are still the only open observations; no new issues found in the latest commits.

Prior finding (PRRT_kwDOAAMl7c6sf6fO) — TUI !text.startsWith("Error:") heuristic: still open and unchanged. The heuristic works correctly for the ToolExecutionException path (where executeShared always returns "Error: ..."), but any tool whose successful response happens to start with "Error:" would incorrectly suppress recordSuccessfulAnswer. Non-blocking as noted previously — the implementation is still better than counting failed calls.

New observation (nit): KameletTools.kameletCache is an unbounded ConcurrentHashMap keyed by the user-supplied kameletsVersion string. In practice the number of distinct versions queried in a single server lifetime is tiny, and CatalogService.cache uses the same unbounded pattern, so this is consistent. Noting for awareness — if memory ever becomes a concern, a bounded LRU (like RepeatedCallSessions.sessions) would cap it.

State machine (RepeatedToolCalls.repeatOf + recordSuccessfulAnswer): the two increment paths are mutually exclusive — the pre-quota path goes through recordSuccessfulAnswer, the post-quota path through repeatOf's own merge(). No double-write. ✅

Pre-fix test validation:

  • aFailedCallDoesNotCountAgainstTheRetryBudget — would fail without the fix: the old repeatOf incremented on every call, so the third call (count=2) would return a note instead of running; the test's assertThat(...).isNull() on the third call catches that. ✅
  • thirdIdenticalCallIsRepeated in DiagnoseToolsTest — would fail without recordSuccessfulAnswer in DiagnoseTools: count would never increment (peek-only), third call returns null instead of the note. ✅

LGTM on the new commits.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

✅ Generated files are up to date

An earlier CI run reported uncommitted generated changes; the latest run no longer does.

@gnodet-bot gnodet-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.

All prior findings are addressed. LGTM.

Previous findings resolved:

  • ✅ Finding 1 (medium) — CatalogService.loadCatalog() catch block now logs via LOG.debugf(...) before falling back to ToolContext lazy-load.
  • ✅ Finding 2 (low) — startsWith("Error:") nit acknowledged; current implementation is correct for the ToolExecutionException path and an improvement over counting failed calls.

New commits verified — no new issues found. The three bugs (catalog caching, kamelet sort-before-limit, failed-call retry budget) are correctly fixed.

This review was generated by an AI agent, Hermès on behalf of @gnodet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working docs dsl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants