{"record":{"id":"dec08dff67db50d5","repo":"jenkinsci/jenkins","slug":"cannot-build-0-because-its-configuration-has-not","errorCode":null,"errorMessage":"Cannot build {0} because its configuration has not been saved.","messagePattern":"Cannot build (.+?) because its configuration has not been saved\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/hudson/cli/BuildCommand.java","lineNumber":164,"sourceCode":"            SCMTriggerItem item = SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(job);\n            if (item == null)\n                throw new AbortException(job.getFullDisplayName() + \" has no SCM trigger, but checkSCM was specified\");\n            // preemptively check for a polling veto\n            if (SCMDecisionHandler.firstShouldPollVeto(job) != null) {\n                return 0;\n            }\n            if (!item.poll(new StreamTaskListener(stdout, getClientCharset())).hasChanges())\n                return 0;\n        }\n\n        if (!job.isBuildable()) {\n            String msg = Messages.BuildCommand_CLICause_CannotBuildUnknownReasons(job.getFullDisplayName());\n            if (job instanceof ParameterizedJobMixIn.ParameterizedJob && ((ParameterizedJobMixIn.ParameterizedJob) job).isDisabled()) {\n                msg = Messages.BuildCommand_CLICause_CannotBuildDisabled(job.getFullDisplayName());\n            } else if (job.isHoldOffBuildUntilSave()) {\n                msg = Messages.BuildCommand_CLICause_CannotBuildConfigNotSaved(job.getFullDisplayName());\n            }\n            throw new IllegalStateException(msg);\n        }\n\n        Queue.Item item = ParameterizedJobMixIn.scheduleBuild2(job, 0, new CauseAction(new CLICause(Jenkins.getAuthentication2().getName())), a);\n        QueueTaskFuture<? extends Run<?, ?>> f = item != null ? (QueueTaskFuture) item.getFuture() : null;\n\n        if (wait || sync || follow) {\n            if (f == null) {\n                throw new IllegalStateException(BUILD_SCHEDULING_REFUSED);\n            }\n            Run<?, ?> b = f.waitForStart();    // wait for the start\n            stdout.println(\"Started \" + b.getFullDisplayName());\n            stdout.flush();\n\n            if (sync || follow) {\n                try {\n                    if (consoleOutput) {\n                        // read output in a retry loop, by default try only once\n                        // writeWholeLogTo may fail with FileNotFound","sourceCodeStart":146,"sourceCodeEnd":182,"githubUrl":"https://github.com/jenkinsci/jenkins/blob/2e228ff40b14dbc8b14ffbc6edf0e4383cf744fc/core/src/main/java/hudson/cli/BuildCommand.java#L146-L182","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (REST edits config without saving):\n//   curl -X POST --data-binary @config.xml $JENKINS/job/myjob/config.xml\n//   java -jar jenkins-cli.jar build myjob   // fails: config not saved\n//\n// after: also trigger a real save via the UI 'Save' button, or in code:\n//   TopLevelItem job = Jenkins.get().getItemByFullName(\"myjob\", AbstractProject.class);\n//   job.updateByXml(/* new config */);\n//   job.save();   // clears hold-off-build-until-save\n//   java -jar jenkins-cli.jar build myjob","handlingStrategy":"validation","validationCode":"// Before invoking the CLI build, ensure the job is buildable and not held off\n// Job<? extends Job,? extends Build> job = jenkins.getItemByFullName(name, Project.class);\nif (job == null) throw new IllegalStateException(\"No such job: \" + name);\nif (!job.isBuildable()) {\n    if (job instanceof ParameterizedJobMixIn.ParameterizedJob\n            && ((ParameterizedJobMixIn.ParameterizedJob) job).isDisabled())\n        throw new IllegalStateException(job.getFullDisplayName() + \" is disabled\");\n    if (job.isHoldOffBuildUntilSave()) {\n        job.save();   // clear the hold-off flag first\n    } else {\n        throw new IllegalStateException(job.getFullDisplayName() + \" is not buildable\");\n    }\n}\n// safe to build now","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["jenkins","cli","build","configuration","save"],"backgroundTag":null,"analyzedSha":"2e228ff40b14dbc8b14ffbc6edf0e4383cf744fc","analyzedAt":"2026-08-14T07:07:15.274Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}