Maven Builds
A Maven build can print thousands of lines. Send that to an agent or into your run history and the signal drowns. The Maven tool (valdr-tools.user.maven) from the starter pack runs your build in a workflow step and returns only what matters: whether the build passed, a short summary, and the errors that caused a failure. The full log stays on the workflow host for when you need it.
Because a failed build comes back as a result, not a crash, your workflow decides what happens next. Send the errors back to the agent that wrote the code, stop for a person, or carry on. You choose the process.
It has two actions: version checks the Java and Maven setup, and build runs the goals you choose. The tool runs ./mvnw when the working directory has one (it must be executable), otherwise mvn from the host’s PATH. The workflow host needs Node, which runs the tool itself, and a JDK, plus Maven unless your project ships mvnw. Run version first to confirm the host is ready.
Choose goals and options
The tool runs in the step’s working directory, which defaults to the workflow’s worktree or project repository. If your pom.xml lives somewhere else, set Working directory (cwd). In the Builder, enter the build inputs in Inputs JSON object, for example {"goals": ["clean", "verify"], "properties": {"skipITs": true}}. In YAML they sit under the step’s inputs:
inputs:
goals: [clean, verify]
properties:
skipITs: true
profiles: [development]
modules: [service]
alsoMake: true
timeoutSeconds: 240goals: any lifecycle phases or plugin goals your project defines, such as[package]or[clean, verify]. The default is[package].properties: string, number, or boolean values, each passed as one-Dname=valueargument, such astest: MyTestorskipTests: true.profilesactivates Maven profiles (-P).modulesbuilds only the listed modules (-pl); addalsoMake: trueto build the modules they depend on as well (-am). Leave these out to build the whole project with its default profiles.javaHome: an optional absolute path to the JDK for this build. Otherwise Maven uses the host’s Java configuration.timeoutSeconds: see Set a timeout.
Values are passed to Maven directly, never through a shell. The tool always runs Maven in batch mode without download progress or color codes, so the log stays readable.
Set a timeout
Builds time out after 240 seconds unless you set timeoutSeconds, from 1 to 270. On a timeout or cancellation, Valdr attempts to stop Maven and its child processes; detached or external work may continue. A user tool can run for at most 300 seconds, so for builds that need longer, run Maven from a Command step, whose timeout you can raise, or split the build with modules. The first build on a fresh host also downloads dependencies, so give it extra time.
Read the result
Every build that runs to the end returns:
| Field | What it holds |
|---|---|
result | passed when Maven exits zero, failed when it exits nonzero |
summary | One line, such as Maven clean verify failed (exit 1). |
errorExcerpt | The first error lines Maven printed, up to ten, without its closing boilerplate. If Maven printed none, the last lines of its error output. Empty when the build passed |
exitCode | Maven’s exit code |
durationMs | How long the build took |
logPath, logTruncated | Where the full log is, and whether it was cut short |
testSummary | The last test summary line Maven reported, when there was one |
For compile errors, the excerpt holds the file, line, and message. For test failures, it usually holds the failing test and its assertion. testSummary isn’t a total across modules, and a passing build doesn’t mean tests ran if your goals or properties skipped them.
A failed result covers anything that made Maven exit nonzero, including environment problems such as a misconfigured JDK or an unreachable repository. Open logPath when the excerpt isn’t enough. The build log is an owner-only file in the host’s temporary folder, capped at 2 MiB. It isn’t attached to the run, and it disappears when the host clears temporary files, so copy it if you need to keep it.
The starter applies best-effort redaction to build output, including the excerpt and log. It may miss secrets printed by your build or plugins. You are responsible for what your build returns and logs; review and remove sensitive values before sharing output or sending it to an agent. Valdr does not apply further redaction to the tool’s successful data result.
The step itself stops only when Maven can’t run to the end: Node, Maven, or mvnw can’t start, the build times out, or its process is killed. Fix the cause and check whether the selected goals already changed an external system before choosing Retry step. A retry reuses the step’s inputs, so to give a build more time, raise timeoutSeconds and start a new run.
Send a failed build back to the agent
Want the agent to fix its own build breaks? Add an Outcome route after the build that loops back to the agent’s session when the build fails. It’s one option: the same result can just as easily stop for a person or let the run carry on. Choose goals and plugins that can safely run on each pass; see retry behavior.
This fragment assumes an earlier session step, launch_agent, that started the agent in its own worktree (for example action: launch_task with run: false, so this loop sends its first turn) and maps sessionUlid: "$.normalized.session.sessionUlid" and worktreePath: "$.normalized.session.worktreePath":
- key: implement
name: Implement change
kind: session
needs: [launch_agent]
session:
action: input
sessionUlid: "${steps.launch_agent.outputs.sessionUlid}"
prompt: |-
Implement the change. If the last build failed, fix these errors first:
${runtime.loop.previous.outputs.buildErrors}
# An input step waits for the agent's turn to finish before the build runs.
- key: build
name: Build
kind: tool
needs: [implement]
# tool: the pinned Maven build reference the Builder writes for you
cwd: "${steps.launch_agent.outputs.worktreePath}"
inputs:
goals: [clean, verify]
outputs:
result: "$.normalized.data.result"
errors: "$.normalized.data.errorExcerpt"
- key: route_build
name: Route build result
kind: condition
needs: [build]
checks:
- kind: value_equals
value: "${steps.build.outputs.result}"
equals: passed
domain: [passed, failed]
outputs:
buildErrors: "${steps.build.outputs.errors}"
onFailure:
action: loop_back
to: implement
max: 3
exhausted: blockimplementsends the agent a turn in its existing session. On the first pass there’s no previous build, so the error line is empty.buildruns Maven in the agent’s worktree, so it builds the agent’s working copy, including changes it hasn’t committed yet.route_buildchecksresult.domainlists every value it can take, and onfailed,loop_backreturns toimplement.max: 3counts every pass, including the first. If the third build still fails,exhausted: blockstops the run for you; usefailto end it instead.outputs.buildErrorsis what the next pass reads as${runtime.loop.previous.outputs.buildErrors}. MapsummaryorlogPathtoo if you want the agent to see more.
Valdr keeps every attempt, so the run shows each implementation and build pass. When the build passes, the route completes and your workflow moves on to review, commit, or whatever comes next.
Adapt the starter
The Maven tool is a starting point, not a policy. goals takes any phase or plugin goal your project defines, install and deploy included, and the tool runs what you choose.
The starter declares build as retry: idempotent, so Retry step is available after a tool failure such as a timeout. That declaration doesn’t check what your goals or plugins do: a deployment or other external change may have completed before the failure. Review those effects before repeating the build.
In your own copy, use retry: manual when a tool failure after execution starts needs reconciliation. A completed build with a nonzero exit still returns result: failed, so your workflow decides whether to run it again. The retry setting doesn’t prevent an Outcome route from looping back to the build.
Beyond the baseline environment, the tool passes only JAVA_HOME to Maven. ~/.m2/settings.xml and your project’s .mvn/ configuration work as usual. If your build reads other variables, such as MAVEN_OPTS or repository credentials, add their names to inheritEnv in your copy and supply them through ~/.valdr/environment.env or the launch environment. Keep values out of the manifest.
When you change the tool, update its revision in both the manifest and the runner’s request check, and give it your own tool ID if the copy lives in your own pack. Then ship the new revision.
Next step
Import the starter pack and run Maven in a test workflow against a project you know. Using Gradle or another build tool? Copy tools/maven from the starter pack source and adapt it.