Back to list
    Three Jenkinsfile Fixes: Plugin Dependencies, Agent Sizing, and Build Status Design
    Dev Labo
    PRThis article contains advertisements

    Three Jenkinsfile Fixes: Plugin Dependencies, Agent Sizing, and Build Status Design

    23 min read

    One day while working on CI/CD improvements, three small Jenkinsfile fixes piled up on me.

    Taken individually, each one ends with "oh, right" — but lined up together, a common theme emerges.

    "Jenkins has a lot of implicit assumptions."

    So today I'm writing down the record of that triple-fix session.

    Background

    While reviewing and fixing a PR to parallelize the Seed Job Orchestrator (aimed at shortening CI runtime), the Jenkinsfile blew up with an error. From there, two more issues in other jobs surfaced in a chain, and I fixed them all together.

    Fix 1: Dealing with an environment where the lock step isn't available

    What happened

    Inside the Jenkinsfile I was using the lock step to guarantee exclusive execution of the Seed Job.

    lock(resource: "seed-job-${jobName}") {
        def result = build(
            job: "Seed_Jobs/${jobName}",
            wait: true,
            propagate: true
        )
    }

    Running it produced this error:

    java.lang.NoSuchMethodError: No such DSL method 'lock' found among steps [...]

    The error said lock was not in the list of available steps.

    The lock step requires the Lockable Resources Plugin, which wasn't installed in this environment.

    Weighing the options

    I considered three approaches.

    1. Remove lock and add disableConcurrentBuilds() (minimal change)
    2. Replace with throttle (use the Throttle Concurrent Builds Plugin that exists in this environment — but requires pre-defined resources)
    3. Install the Lockable Resources Plugin (run the current code as-is — requires Jenkins admin privileges)

    Honestly, my first thought was "can't we just install the plugin?"

    But admin privileges were a problem, and making something that works in the current environment was more pragmatic.

    Besides, there's a fundamental assumption that "we don't run multiple Seed Job Orchestrators concurrently." If we prevent double-invocation with disableConcurrentBuilds(), that effectively replaces what lock was doing.

    pipeline {
        agent {
            label 'built-in'
        }
    
        options {
            disableConcurrentBuilds()  // <- add this
        }
    
        // ...
    }

    I also removed the lock wrappers from two places (the serial stage and the parallel stage).

    // Before
    lock(resource: "seed-job-${jobName}") {
        def result = build(
            job: "Seed_Jobs/${jobName}",
            wait: true,
            propagate: true
        )
        echo "✓ ${jobName} completed: build #${result.number} (${result.result})"
    }
    
    // After (lock wrapper removed)
    def result = build(
        job: "Seed_Jobs/${jobName}",
        wait: true,
        propagate: true
    )
    echo "✓ ${jobName} completed: build #${result.number} (${result.result})"

    What I learned

    The lock step is a feature of the Lockable Resources Plugin.

    Code written assuming "the plugin is obviously there" can break in a different environment. It's a classic Jenkins thing, but I felt it again.

    disableConcurrentBuilds() is a built-in option, so no plugin required.

    If your goal is "prevent the same pipeline from running twice at once," this is the simpler tool.

    You pick the right tool depending on the use case. In this case it was clearly "prevent the same pipeline from running twice," so disableConcurrentBuilds() was enough.

    Fix 2: Changing the agent label (nano → small)

    Premise: agent labels split by instance size

    First, let me explain why this fix was a one-line change.

    It was because of the design choice to define agent labels split by instance size.

    ec2-fleet-t-nano   -> t-family nano agent pool
    ec2-fleet-t-small  -> t-family small agent pool
    ec2-fleet-t-medium -> t-family medium agent pool

    Because of this design, you can switch instance sizes just by changing the label on the Jenkinsfile side.

    In an environment where labels aren't split by size, this approach doesn't work. You'd have to change the agent configuration itself or take a different route.

    What happened

    Another job (a pipeline that configures GitHub Webhooks) had stopped running reliably on ec2-fleet-t-nano agents.

    // Before
    agent {
        label 'ec2-fleet-t-nano'
    }
    
    // After
    agent {
        label 'ec2-fleet-t-small'
    }

    What I thought about

    When you dynamically spin up agents with EC2 Fleet, instance size is a decision that tends to get deferred. "Let's just start with the cheapest — nano" is a reasonable early call, but as the number of jobs and the amount of work grows, problems start appearing slowly.

    In this case, the job was configuring webhooks against multiple GitHub repositories and ran for a while. The nano was likely resource-constrained.

    "Designing labels split by size" makes this kind of response simple. The fact that this was the design from the start was a win. But if you don't understand that context and just copy the label name, nothing works — so I'm writing it down as a premise.

    Fix 3: SUCCESS vs UNSTABLE

    What happened

    While checking a job that batch-configures GitHub Webhooks across multiple repositories, I noticed this in the logs.

    Total Repositories: 226
    Already Configured: 212
    Success: 0
    Failed: 0
    Timeout: 2
    Error: 12
    Finished: SUCCESS

    Out of 226 repos, 14 failed (2 timeouts, 12 errors) — and yet the build finished as SUCCESS.

    That's a problem. Anyone looking at the CI result concludes "it worked."

    In reality, webhooks aren't configured on some repositories, and that could go unnoticed.

    How I fixed it

    I added logic to mark the build UNSTABLE when there are failures, timeouts, or errors.

    if (summaryJson.failed > 0 || summaryJson.timeout > 0 || summaryJson.error > 0) {
        // Log the list of repos that had problems
        // ...
    
        // Mark build as UNSTABLE when any repository failed/timed out/errored
        echo "⚠️  ${summaryJson.failed} failed, ${summaryJson.timeout} timeout, ${summaryJson.error} error — marking build as UNSTABLE"
        currentBuild.result = 'UNSTABLE'
    }

    I also added an unstable clause to the post block.

    post {
        success {
            echo '✅ Batch webhook configuration completed successfully!'
        }
        unstable {
            echo '⚠️  Batch webhook configuration completed with failures/timeouts/errors. See summary above.'
        }
        failure {
            echo '❌ Failed to execute batch webhook configuration'
        }
    }

    With this, the success message no longer fires when the build is unstable.

    Using Jenkins build statuses correctly

    Jenkins has three build statuses.

    • SUCCESS: everything completed normally
    • UNSTABLE: the build itself completed, but something is off (test failures, partial failures in processing, etc.)
    • FAILURE: the build itself failed

    This case clearly called for UNSTABLE. That status exists to express "the work ran to completion, but some of it didn't go well."

    You set it explicitly with currentBuild.result = 'UNSTABLE'.

    Using the "yellow" that sits between green and red well makes your CI more trustworthy.

    Summary of the day

    FixProblemSolution
    Seed Job Orchestratorlock step unavailable (Lockable Resources Plugin not installed)Replace with disableConcurrentBuilds()
    GitHub Webhooks SettingResource shortage on nano agentsSwitch to small
    Batch Webhook SettingPartial failures still reported as SUCCESSMark as UNSTABLE on failure

    Each fix looks small on its own, but the common pattern is "implicit assumptions being betrayed."

    • Code written on the assumption that a plugin is installed
    • The assumption that the agent can handle the job (plus the context that label-by-size design made it fixable in one line)
    • The assumption that "if the build is green, everything is fine"

    They're all the kind of issue that builds up from stacked "it'll probably be fine" judgments, and then quietly turns into a real problem.

    The third one — build status — I think is especially important if you use CI as a monitoring tool. Using UNSTABLE well lets you accurately express "not completely broken, but something you should look at."

    More to verify. Continuing.

    Further reading

    If you want to learn Jenkins pipeline syntax and plugins systematically, the following book is a useful reference (Japanese).

    For the foundational knowledge around agent sizing and CI execution infrastructure, this one is also recommended.

    Was this article helpful?

    Coffee cup

    If this article helped you organize your thoughts

    a coffee-sized support would be much appreciated.

    ※ This is separate from tipping, but—

    If you'd like to organize similar themes in your own context, I also offer dialogue sessions as a form of thought organization.

    About Dialogue Sessions