Skip to content

t1k:jenkins-job

FieldValue
Modulet1k-devops
Version1.4.1
Effortmedium
Tools—

Keywords: batch jobs, build job, ci, clone job, cocos, config.xml, createItem, crumb, discord thread, jenkins, job, pipeline, playable, template job

/t1k:jenkins-job
<jenkins-folder> [--from <template-job>] [--projects <dir>] [--set PARAM=value]

Jenkins Job Creation — clone a template job via config.xml

Section titled “Jenkins Job Creation — clone a template job via config.xml”

Jenkins jobs are just an XML document. GET <job>/config.xml gives you the whole thing; POST <folder>/createItem?name=X with a mutated copy creates a new job. This skill codifies that round-trip: read template → mutate → create → verify off the server.

Everything runs through scripts/jenkins-job.cjs (Node ≥18, uses built-in fetch; no curl, no shell quoting traps on Windows).

IntentPath
”Create one job from template X”Workflow, skip step 2
”Create one job per project in this folder”Workflow full, incl. Batch rules
”What jobs already exist / did it work?”list + verify — see Commands
”Change a param on jobs that already exist”update — see Commands. Always --dry-run first.
”Delete a job I created by mistake”Read Gotchas first — you probably cannot.

Never ask the user for a Jenkins token before checking whether they have already supplied one. jenkins-job.cjs resolves the credential in this order, and only the last step involves the user:

  1. Env — JENKINS_URL / JENKINS_USER / JENKINS_TOKEN, if all three are set.
  2. The registered jenkins MCP server — t1k-mcp-management’s install-jenkins.sh stores the same credential in ~/.claude.json as Authorization: Basic base64("<user>:<api-token>"). The script reads it from there automatically. A user whose Jenkins MCP tools work has already authenticated; asking them to mint a second API token is a bug.
  3. Ask — only when neither exists. The script says so explicitly rather than failing with a bare missing env.

Step 2 is host-guarded: the MCP credential is reused only when its origin matches JENKINS_URL, or supplies the origin itself. A mismatch is refused, never silently retargeted — one host’s token must not be sent to another.

Credentials are never passed as argv (they leak into shell history and ps) and never echoed back to the user. Setting env explicitly still works and always wins:

Terminal window
export JENKINS_URL="https://jenkins.example.org"
export JENKINS_USER="<username>"
export JENKINS_TOKEN="<api-token>" # API token IS the basic-auth password

Only when all three resolution steps come up empty, get a token at <JENKINS_URL>/user/<username>/security → Add new Token.

  1. Pull the template. get-config <folder> <template> --out template.xml. Read it — you need to know which parameters exist, the git remote, the branch spec, the scriptPath, and whether a GitHubPushTrigger is present.
  2. Gather per-project facts (batch only). For each project dir, read the real values instead of guessing: git remote (git -C <dir> remote get-url origin), branch, and any project-name field the template’s parameters expect. Present the collected table to the user before creating anything.
  3. ASK for the Discord thread ID. HARD REQUIREMENT — see Never inherit a thread ID.
  4. Ask about the open decisions with AskUserQuestion, batched into one call. At minimum: thread ID (step 3), keep-or-strip the push trigger, and any parameter whose template default is demonstrably wrong for the target projects. State the evidence for “wrong” — don’t just assert it.
  5. Smoke one job. Generate + create exactly ONE, then verify it against the server and show the user the result. Wait for approval before the rest. (rules/preview-first-batch.md)
  6. Create the remainder, then verify every job in one pass and print a table.
Terminal window
S=~/.claude/skills/t1k-jenkins-job/scripts/jenkins-job.cjs
node $S list <folder> [--grep <substr>] # existing job names (collision check)
node $S get-config <folder> <job> --out <file> # pull a template
node $S gen --base <template.xml> --out <job.xml> \
--set PARAM_NAME=value --set OTHER=value \
--repo [email protected]:Org/Repo.git --branch '*/develop' \
--script-path Jenkinsfile/Cocos \
--description "..." [--strip-triggers]
node $S create <folder> <name> --from <job.xml> # refuses if the job exists
node $S update <folder> <name> --set PARAM=value \
[--repo ...] [--branch ...] [--dry-run] # GET config -> mutate -> POST back
node $S verify <folder> <name> --expect PARAM=value \
--expect-repo <url> --expect-triggers yes|no # reads config.xml BACK off the server

gen is offline and deterministic. --set targets a parameter’s <defaultValue> by its <name>, and fails loudly if the parameter is absent or has no <defaultValue> — a silent no-op would ship a job with the template’s value still in it.

update mutates a live job in place: it re-reads the job’s current config (not your local template), applies the same --set rules, and POSTs it back to <job>/config.xml. Old values of any param whose name matches TOKEN|SECRET|PASSWORD|KEY print as <redacted>. Run --dry-run first, then verify after — a 200 on the POST does not prove the field landed.

A template job’s PARAM_DISCORD_THREAD_ID (or any notification target: webhook URL, chat room, channel ID) points at whatever thread the template’s author was using. Copying it means every new job posts build notifications into someone else’s thread.

Always AskUserQuestion for the thread ID before creating any job. Do not reuse a value because it appeared in the template, in a previous run of this skill, or earlier in the conversation — unless the user names that exact value in this request. If the user does not know it yet, stop and wait; do not create the jobs with a placeholder intending to fix it later.

Same rule for PARAM_DISCORD_WEBHOOK_URL and PARAM_GOOGLE_CHAT_WEBHOOK_URL when they are non-empty in the template.

  • Collision check first — list <folder> and compare case-insensitively. Jenkins job names are case-sensitive in the API, so Test and test can coexist and confuse everyone later.
  • Derive, never assume. Read the project name from the project’s own manifest / folder name and apply whatever transform the template’s parameter documents (its <description> usually says).
  • Uniform ≠ verified. Even when 11 projects look identical, read all 11. One outlier that you assumed away becomes a broken job.
  • Report what you did NOT set. Any template parameter you left at its default, but which is arguably per-project, goes in the final report explicitly.
  • A parameter with an empty default has NO <defaultValue> element at all. Jenkins omits the element entirely rather than writing <defaultValue></defaultValue>. Any “find the name, then grab the next <defaultValue>” regex therefore walks past the closing tag and hits the next parameter’s default — writing your value into the wrong parameter and silently destroying its contents. Always scope the match to a single <hudson.model.*ParameterDefinition> element, and insert the <defaultValue> element when it is missing. (findParamBlock in the script does this.)
  • Never verify with the same helper you wrote with. The bug above passed verification because read and write shared the broken locator — a self-consistent lie. For any change that matters, do a second read with an independent parser (e.g. split into parameter blocks and match exactly) and compare. verify reporting green proves the two code paths agree, not that the config is right.
  • You may not be able to delete what you create. Job/Create and Job/Delete are separate Jenkins permissions and many CI accounts have only the first. A throwaway “test job” then becomes permanent litter needing an admin. Never create a scratch job to try something out — smoke-test on a job the user actually wants. Check with POST <job>/doDelete; a 403 body says is missing the Job/Delete permission.
  • A secret POSTed into a password parameter stays plaintext in config.xml. Jenkins encrypts a PasswordParameterDefinition default ({AQAAABAA…}) when the job is saved through the UI, but a value written via POST config.xml is stored verbatim and reads back in the clear to anyone with Job/Read. It still works — Jenkins accepts an unencrypted Secret string — but if the value is sensitive, tell the user, and have them open the job in the UI and hit Save once to re-encrypt.
  • CSRF crumb must travel with its session cookie. GET /crumbIssuer/api/json returns the crumb and sets a session cookie; sending the crumb header without that cookie gets 403 “No valid crumb was included”. The script handles this — if you hand-roll curl, use -c/-b with one cookie jar for both requests.
  • createItem returns 200 with an empty body on success. Do not read “no output” as failure. Conversely a 200 does not prove the config landed correctly — always verify.
  • URL-encode [ and ] in tree= queries. ?tree=jobs[name] makes curl abort with bad range in URL position N. Use tree=jobs%5Bname%5D.
  • require() cannot read .meta / .scene files. Node only resolves .json/.js; a require('./X.scene.meta') fails and, if you swallowed the error, prints ? for every row. Use JSON.parse(fs.readFileSync(p,'utf8')).
  • Piping a large curl response into head on Windows gives curl: (23) Failure writing output and truncated data. Write with -o <file> and read the file.
  • [email protected]: remotes need a matching credentialsId already configured in Jenkins. The template’s credential is inherited by the clone — if the new repos live under a different org, the SSH key may not grant access, and that surfaces only at first build.
  • Deciding to keep the template’s GitHubPushTrigger is a real choice. Kept, every push to the tracked branch starts a build across all the jobs you just created. Ask.

Creating Jenkins jobs from a template job’s config.xml, and changing parameter defaults / git remote / branch on jobs that already exist, plus the read-only list / get-config / verify support around it. Not for: deleting jobs, triggering or aborting builds, managing credentials/nodes/plugins, or editing Jenkinsfiles and shared pipeline libraries.

Three facts worth recording here, because rediscovering each one costs real time.

  1. The Jenkins MCP and this script share ONE credential under two names. MCP registration lives in the t1k-mcp-management skill → scripts/install-jenkins.sh, which reads JENKINS_USERNAME / JENKINS_API_TOKEN and persists them to ~/.claude.json as a Basic auth header; jenkins-job.cjs reads the same values as JENKINS_USER / JENKINS_TOKEN. Two consequences, and the first is the one that gets missed: a working Jenkins MCP means the credential is already on disk — resolve it (see Setup) instead of asking the user for a new token. Second, mixing the two NAMES presents as a 401 that reads like a bad token.
  2. The Jenkins MCP Server plugin has NO job-creation tool (verified against the plugin’s full built-in tool list) — it is read + trigger only. Job creation must go through POST /createItem (what this skill’s create command does). Without this note, an agent hunts for an MCP creation tool that does not exist.
  3. gen / create never touch GithubProjectProperty’s <projectUrl>. A cloned job’s GitHub link still points at the template’s repo even though the git checkout remote is rewritten correctly (verified by diffing a generated config against BaseJob). Cosmetic, but confusing.