t1k:jenkins-job
| Field | Value |
|---|---|
| Module | t1k-devops |
| Version | 1.4.1 |
| Effort | medium |
| Tools | — |
Keywords: batch jobs, build job, ci, clone job, cocos, config.xml, createItem, crumb, discord thread, jenkins, job, pipeline, playable, template job
How to invoke
Section titled “How to invoke”/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).
Decision Tree
Section titled “Decision Tree”| Intent | Path |
|---|---|
| ”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:
- Env —
JENKINS_URL/JENKINS_USER/JENKINS_TOKEN, if all three are set. - The registered
jenkinsMCP server —t1k-mcp-management’sinstall-jenkins.shstores the same credential in~/.claude.jsonasAuthorization: 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. - 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:
export JENKINS_URL="https://jenkins.example.org"export JENKINS_USER="<username>"export JENKINS_TOKEN="<api-token>" # API token IS the basic-auth passwordOnly when all three resolution steps come up empty, get a token at
<JENKINS_URL>/user/<username>/security → Add new Token.
Workflow
Section titled “Workflow”- 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, thescriptPath, and whether aGitHubPushTriggeris present. - 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. - ASK for the Discord thread ID. HARD REQUIREMENT — see Never inherit a thread ID.
- 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. - Smoke one job. Generate + create exactly ONE, then
verifyit against the server and show the user the result. Wait for approval before the rest. (rules/preview-first-batch.md) - Create the remainder, then
verifyevery job in one pass and print a table.
Commands
Section titled “Commands”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 templatenode $S gen --base <template.xml> --out <job.xml> \ --set PARAM_NAME=value --set OTHER=value \ --script-path Jenkinsfile/Cocos \ --description "..." [--strip-triggers]node $S create <folder> <name> --from <job.xml> # refuses if the job existsnode $S update <folder> <name> --set PARAM=value \ [--repo ...] [--branch ...] [--dry-run] # GET config -> mutate -> POST backnode $S verify <folder> <name> --expect PARAM=value \ --expect-repo <url> --expect-triggers yes|no # reads config.xml BACK off the servergen 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.
Never inherit a thread ID
Section titled “Never inherit a thread ID”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.
Batch rules
Section titled “Batch rules”- Collision check first —
list <folder>and compare case-insensitively. Jenkins job names are case-sensitive in the API, soTestandtestcan 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.
Gotchas
Section titled “Gotchas”- 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. (findParamBlockin 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.
verifyreporting green proves the two code paths agree, not that the config is right. - You may not be able to delete what you create.
Job/CreateandJob/Deleteare 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 withPOST <job>/doDelete; a 403 body saysis missing the Job/Delete permission. - A secret POSTed into a password parameter stays plaintext in
config.xml. Jenkins encrypts aPasswordParameterDefinitiondefault ({AQAAABAA…}) when the job is saved through the UI, but a value written viaPOST config.xmlis stored verbatim and reads back in the clear to anyone with Job/Read. It still works — Jenkins accepts an unencryptedSecretstring — 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/jsonreturns 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/-bwith one cookie jar for both requests. createItemreturns 200 with an empty body on success. Do not read “no output” as failure. Conversely a 200 does not prove the config landed correctly — alwaysverify.- URL-encode
[and]intree=queries.?tree=jobs[name]makes curl abort withbad range in URL position N. Usetree=jobs%5Bname%5D. require()cannot read.meta/.scenefiles. Node only resolves.json/.js; arequire('./X.scene.meta')fails and, if you swallowed the error, prints?for every row. UseJSON.parse(fs.readFileSync(p,'utf8')).- Piping a large curl response into
headon Windows givescurl: (23) Failure writing outputand truncated data. Write with-o <file>and read the file. [email protected]:remotes need a matchingcredentialsIdalready 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
GitHubPushTriggeris 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.
Related tooling
Section titled “Related tooling”Three facts worth recording here, because rediscovering each one costs real time.
- The Jenkins MCP and this script share ONE credential under two names. MCP registration lives
in the
t1k-mcp-managementskill →scripts/install-jenkins.sh, which readsJENKINS_USERNAME/JENKINS_API_TOKENand persists them to~/.claude.jsonas a Basic auth header;jenkins-job.cjsreads the same values asJENKINS_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. - 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’screatecommand does). Without this note, an agent hunts for an MCP creation tool that does not exist. gen/createnever touchGithubProjectProperty’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.