Skip to content

fix: cache failed build output so gradle-get-details works for failures#137

Open
holloandris wants to merge 1 commit into
thecombatwombat:masterfrom
holloandris:fix/cache-failed-build-output
Open

fix: cache failed build output so gradle-get-details works for failures#137
holloandris wants to merge 1 commit into
thecombatwombat:masterfrom
holloandris:fix/cache-failed-build-output

Conversation

@holloandris

Copy link
Copy Markdown

Problem

gradle-build throws BUILD_FAILED (in GradleAdapter.build) before handleGradleBuildTool generates a buildId or caches the output. So when a build fails:

  • No buildId is produced and nothing is cached → gradle-get-details has nothing to fetch.
  • The error's own "Check gradle-get-details for full error output" suggestion is a dead end for failures.
  • No log file exists on disk (output is only ever kept in the in-memory cache, which the failure path skips).

The net effect: the build you most need to inspect — a failing one — is the only one whose full output you can't retrieve.

Fix

  • Carry the full build output on the thrown error's context (fullOutput).
  • In handleGradleBuildTool, catch BUILD_FAILED, cache { fullOutput, result, operation } under a fresh buildId (identical shape to the success path), and rethrow the same BUILD_FAILED error with that buildId in contextwithout inlining the bulky fullOutput, so the error payload stays small.

gradle-get-details { id, detailType: "errors" } now returns compiler errors for failed builds exactly as it does for successful ones. The error code/contract is unchanged; the token-optimized design is preserved (raw logs are fetched on demand, not dumped into the error).

Test

Adds a round-trip test: a failing gradle-build → assert it rethrows BUILD_FAILED with a buildId (and no inlined fullOutput) → feed that id to gradle-get-details and assert the compiler error line is returned.

Full suite (673 tests) + contract & token-snapshot checks pass.

gradle.build() throws BUILD_FAILED before handleGradleBuildTool generates
a buildId or caches output, so a failed build leaves nothing for
gradle-get-details to fetch — the one build you most need to inspect is
the only one you can't, and the tool's own "Check gradle-get-details"
suggestion is a dead end for failures.

Carry the full output on the thrown error's context, then in the tool
handler catch BUILD_FAILED, cache {fullOutput, result, operation} under a
fresh buildId (same shape as the success path), and rethrow the same
BUILD_FAILED error with that buildId in context (and without the bulky
fullOutput inlined). gradle-get-details { id, detailType: "errors" } now
returns the compiler errors for failed builds exactly as for successful
ones, preserving the token-optimized contract instead of dumping raw logs.

Adds a round-trip test (failure -> cache -> get-details errors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the gradle-get-details dead end for failing builds by caching the full build output before rethrowing BUILD_FAILED, giving it a buildId that clients can use to retrieve compiler errors — the same retrieval path as successful builds.

  • GradleAdapter.build now attaches fullOutput to the thrown BUILD_FAILED error's context, so the tool handler can pull it out for caching.
  • handleGradleBuildTool catches BUILD_FAILED, stores { fullOutput, result, operation } in the cache under a fresh buildId, then rethrows a new BUILD_FAILED error with only { buildResult, buildId } in context — fullOutput is intentionally stripped so the error payload remains small and never reaches the server's toToolError() serialiser.
  • A new round-trip test validates the full failure → cache → retrieval flow.

Confidence Score: 5/5

Safe to merge — the change is self-contained and the rethrown error deliberately omits fullOutput so the MCP serialiser never sees the raw build log.

The failure-path catch block correctly caches output and rethrows a lean error; the success path is untouched; toToolError() on the rethrown error only exposes { buildResult, buildId }. The new round-trip test covers the critical path end-to-end and the shape of the cached entry matches exactly what gradle-get-details expects for every detailType.

No files require special attention.

Important Files Changed

Filename Overview
src/tools/gradle-build.ts Wraps the build call in a try/catch; on BUILD_FAILED caches the output and rethrows a new error with buildId but without fullOutput, keeping the MCP response payload small.
src/adapters/gradle.ts Attaches fullOutput to the BUILD_FAILED error context so the tool layer can cache it; the original success-path return is unchanged.
src/types/errors.ts Adds fullOutput and buildId to ErrorContext; minimal, non-breaking type extension.
tests/tools/gradle-build-failure.test.ts New round-trip test covering BUILD_FAILED caching and retrieval via gradle-get-details; mock shape and assertions match the implementation correctly.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant Server as server.ts
    participant Tool as handleGradleBuildTool
    participant Adapter as GradleAdapter.build
    participant Cache as CacheManager

    Client->>Server: "gradle-build { operation, ... }"
    Server->>Tool: handleGradleBuildTool(input, context)
    Tool->>Adapter: context.gradle.build(operation, ...)
    Adapter-->>Tool: "throws BUILD_FAILED { buildResult, fullOutput }"

    Note over Tool: catch BUILD_FAILED
    Tool->>Cache: "cache.set(buildId, { fullOutput, result, operation })"
    Tool-->>Server: "rethrows BUILD_FAILED { buildResult, buildId } (no fullOutput)"
    Server-->>Client: "error { code: BUILD_FAILED, suggestion: gradle-get-details id }"

    Client->>Server: "gradle-get-details { id: buildId, detailType: errors }"
    Server->>Cache: cache.get(buildId)
    Cache-->>Server: "{ fullOutput, result, operation }"
    Server-->>Client: "{ detailType: errors, errors: ..., ... }"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant Server as server.ts
    participant Tool as handleGradleBuildTool
    participant Adapter as GradleAdapter.build
    participant Cache as CacheManager

    Client->>Server: "gradle-build { operation, ... }"
    Server->>Tool: handleGradleBuildTool(input, context)
    Tool->>Adapter: context.gradle.build(operation, ...)
    Adapter-->>Tool: "throws BUILD_FAILED { buildResult, fullOutput }"

    Note over Tool: catch BUILD_FAILED
    Tool->>Cache: "cache.set(buildId, { fullOutput, result, operation })"
    Tool-->>Server: "rethrows BUILD_FAILED { buildResult, buildId } (no fullOutput)"
    Server-->>Client: "error { code: BUILD_FAILED, suggestion: gradle-get-details id }"

    Client->>Server: "gradle-get-details { id: buildId, detailType: errors }"
    Server->>Cache: cache.get(buildId)
    Cache-->>Server: "{ fullOutput, result, operation }"
    Server-->>Client: "{ detailType: errors, errors: ..., ... }"
Loading

Reviews (2): Last reviewed commit: "fix: cache failed build output so gradle..." | Re-trigger Greptile

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