jenkinsci/jenkins · error · IllegalStateException
Cannot build {0} because its configuration has not been save
Error message
Cannot build {0} because its configuration has not been saved. What it means
Thrown by `BuildCommand.run()` when the target job reports `!job.isBuildable()` and `job.isHoldOffBuildUntilSave()` is true, resolving the localized message `BuildCommand_CLICause_CannotBuildConfigNotSaved`. Jenkins marks a job as 'hold off until save' after its config.xml is written (e.g. via the POST config.xml REST endpoint or direct disk edit) without a programmatic `save()`, so it refuses to queue builds until the config is re-saved through the UI or API. The error surfaces as an IllegalStateException from the CLI `build` command.
Source
Thrown at core/src/main/java/hudson/cli/BuildCommand.java:164
SCMTriggerItem item = SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(job);
if (item == null)
throw new AbortException(job.getFullDisplayName() + " has no SCM trigger, but checkSCM was specified");
// preemptively check for a polling veto
if (SCMDecisionHandler.firstShouldPollVeto(job) != null) {
return 0;
}
if (!item.poll(new StreamTaskListener(stdout, getClientCharset())).hasChanges())
return 0;
}
if (!job.isBuildable()) {
String msg = Messages.BuildCommand_CLICause_CannotBuildUnknownReasons(job.getFullDisplayName());
if (job instanceof ParameterizedJobMixIn.ParameterizedJob && ((ParameterizedJobMixIn.ParameterizedJob) job).isDisabled()) {
msg = Messages.BuildCommand_CLICause_CannotBuildDisabled(job.getFullDisplayName());
} else if (job.isHoldOffBuildUntilSave()) {
msg = Messages.BuildCommand_CLICause_CannotBuildConfigNotSaved(job.getFullDisplayName());
}
throw new IllegalStateException(msg);
}
Queue.Item item = ParameterizedJobMixIn.scheduleBuild2(job, 0, new CauseAction(new CLICause(Jenkins.getAuthentication2().getName())), a);
QueueTaskFuture<? extends Run<?, ?>> f = item != null ? (QueueTaskFuture) item.getFuture() : null;
if (wait || sync || follow) {
if (f == null) {
throw new IllegalStateException(BUILD_SCHEDULING_REFUSED);
}
Run<?, ?> b = f.waitForStart(); // wait for the start
stdout.println("Started " + b.getFullDisplayName());
stdout.flush();
if (sync || follow) {
try {
if (consoleOutput) {
// read output in a retry loop, by default try only once
// writeWholeLogTo may fail with FileNotFoundView on GitHub (pinned to 2e228ff40b)
Solutions
- Open the job in the Jenkins UI and click 'Save' (or POST to /job/<job>/config.xml with the full config plus a proper save), which clears the hold-off flag.
- Programmatically call `job.save()` (Jenkins API) after any config change so isHoldOffBuildUntilSave() returns false before invoking the build command.
- Verify with `job.isBuildable()` and `job.isHoldOffBuildUntilSave()` in a script before submitting the CLI build, and surface a clear message instead of letting the IllegalStateException escape.
Example fix
// before (REST edits config without saving):
// curl -X POST --data-binary @config.xml $JENKINS/job/myjob/config.xml
// java -jar jenkins-cli.jar build myjob // fails: config not saved
//
// after: also trigger a real save via the UI 'Save' button, or in code:
// TopLevelItem job = Jenkins.get().getItemByFullName("myjob", AbstractProject.class);
// job.updateByXml(/* new config */);
// job.save(); // clears hold-off-build-until-save
// java -jar jenkins-cli.jar build myjob Defensive patterns
Strategy: validation
Validate before calling
// Before invoking the CLI build, ensure the job is buildable and not held off
// Job<? extends Job,? extends Build> job = jenkins.getItemByFullName(name, Project.class);
if (job == null) throw new IllegalStateException("No such job: " + name);
if (!job.isBuildable()) {
if (job instanceof ParameterizedJobMixIn.ParameterizedJob
&& ((ParameterizedJobMixIn.ParameterizedJob) job).isDisabled())
throw new IllegalStateException(job.getFullDisplayName() + " is disabled");
if (job.isHoldOffBuildUntilSave()) {
job.save(); // clear the hold-off flag first
} else {
throw new IllegalStateException(job.getFullDisplayName() + " is not buildable");
}
}
// safe to build now Prevention
- Always save a job through the UI or via Item.save() after editing config.xml, never leave it in hold-off state.
- In CI scripts, assert job.isBuildable() && !job.isHoldOffBuildUntilSave() before submitting a build.
When it happens
Trigger: Running `java -jar jenkins-cli.jar build <job>` (plain or with -w/-s/-f) against a job whose `config.xml` was modified outside the normal save flow. Specifically the code path at line 157-164: `isBuildable()` is false and the `isHoldOffBuildUntilSave()` branch wins over the disabled branch.
Common situations: Editing a job's config.xml on disk and reloading, using the Jenkins REST `POST /job/<job>/config.xml` which sets the hold-off flag, or a plugin that mutates config without calling `Item.save()`. Also after a Jenkins restart that reloads jobs from disk without re-saving them.
Related errors
- {} is not parameterized but the -p option was specified.
- {} has no SCM trigger, but checkSCM was specified
- Cannot build {0} for unknown reasons.
- Cannot build {0} because it is disabled.
- Build scheduling Refused by an extension, hence not in Queue
AI-assisted analysis of jenkinsci/jenkins@2e228ff40b (2026-08-14).
Data as JSON: /api/errors/dec08dff67db50d5.
Report an issue: GitHub.