This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This is not an application with source code to build/test — it's a library of reusable GitHub Actions workflows (.github/workflows/*.yml) and composite actions (.github/actions/*/action.yml) that Spring projects (Gradle or Maven) call via uses: spring-io/spring-github-workflows/.github/workflows/<file>.yml@main. There is no compiler, package manager, or test suite. "Development" means editing YAML and reasoning about GitHub Actions semantics.
samples/*.yml contains example caller workflows referenced from README.md (each has a permalinked line range in the README pointing at a specific commit — when a sample changes meaningfully, consider whether the README permalink should be updated too).
There is no CI that lints or tests this repo's own workflows. When editing YAML:
- Check indentation and
${{ }}expression syntax carefully — GitHub Actions fails at workflow-parse time with often-unhelpful errors. - Reusable workflows (
on: workflow_call) declare their contract viainputs/secrets/outputsat the top — keep these in sync with what the jobs below actually consume, and updateREADME.mdwhen adding/removing/renaming any of them. - References to sibling reusable workflows within this repo use the local path form
uses: ./.github/workflows/x.yml(works because caller and callee are checked out together at runtime for the top-level workflow); references to composite actions in this repo from within a reusable workflow use the full formuses: spring-io/spring-github-workflows/.github/actions/x@main(composite actions can't be referenced by local path from a called workflow). Follow whichever form the surrounding file already uses. - External dependencies are pinned to a version tag (e.g.
spring-io/spring-release-actions/...@0.0.5,actions/checkout@v7) except this repo's own actions, which are pinned to@mainin source and get rewritten to a release tag only during this repo's own release process (see below) — never hand-edit those to a version tag.
.github/workflows/release-workflows.yml is manually dispatched to cut a release of this repo (distinct from the Spring project release workflow below, which releases consumer projects). It computes the next integer tag from the latest vN tag, rewrites @main → @vN for local composite-action references across .github/workflows/*.yml, commits/tags/pushes, generates a GitHub release, then reverts the rewrite back to @main for continued development. Don't manually pin local action refs to a version — this script owns that.
The most complex piece is the Gradle/Maven release workflow chain, invoked by a consumer project's own caller workflow (see samples/release-with-gradle.yml). It's a tree of nested workflow_call reusable workflows (already 3 levels deep from the caller — do not add another caller layer on top, see the README warning):
spring-artifactory-gradle-release.yml (or -maven-release.yml)
├─ spring-find-release-version.yml → resolves the scheduled Milestone to release
│ └─ actions/spring-compute-milestone-to-release (handles hotfix vs. normal versioning)
├─ spring-pre-release.yml → cancels the run if the milestone has open issues or last SNAPSHOT build failed
├─ spring-artifactory-gradle-build.yml (composite action) / inline Maven build → stages artifacts to libs-staging-local
├─ (verify-staged job, inline) → actions/spring-dispatch-workflow-and-wait dispatches the consumer's
│ verifyStagedWorkflow (default verify-staged-artifacts.yml) and polls until done
├─ spring-artifactory-promote-release.yml → `jfrog rt build-promote` to libs-milestone-local or libs-release-local
├─ spring-artifactory-deploy-to-maven-central.yml (skipped if bundleName input given)
├─ spring-enterprise-release-bundle.yml (used instead, when bundleName input given)
└─ spring-finalize-release.yml → tags, bumps to next SNAPSHOT, pushes the commit + tag. Ends there.
Pushing that tag is a separate trigger, not a job in the tree above: spring-post-release.yml is a push: tags-triggered reusable workflow (its own caller, wired up once per consumer repo alongside the release caller — see README) that derives the milestone/hotfix from the pushed tag name and does the rest — changelog generation (spring-io/github-changelog-generator), GH release creation, milestone close, spring.io website update, chat announcement, next-milestone creation.
Because it's tag-triggered rather than job-graph-triggered, the same workflow fires identically whether spring-finalize-release.yml pushed the tag or something external did (e.g. a release train orchestrator) — do not re-add a direct job-graph call from the pipeline above to spring-post-release.yml, that would double-fire it for every self-release (the original tag push already triggers it once).
Key conventions this pipeline assumes about the calling project (documented in more detail in README.md):
- Version scheme:
major.minor.patch[-M{n}|-RC{n}], SNAPSHOT suffixed-SNAPSHOT. - A GitHub Milestone must exist with a title exactly matching the version to release, and must have a due date set — undue/missing milestones cause the run to self-cancel with a warning rather than fail loudly (search for
gh run cancelto find these guard points). - Everything downstream keys off
needs.release-version.outputs.releaseVersion/hotfix— trace a value's origin by followingneeds.<job>.outputs.*back tospring-find-release-version.yml.
spring-cut-release-branch.yml is the separate, simpler workflow that creates a new release/<version> branch + milestone from a ref (used when branching off a maintenance line, not part of the release chain above).
These are standalone (no nesting) and can be reasoned about in isolation:
spring-gradle-pull-request-build.yml/spring-maven-pull-request-build.yml— PR check (gradlew check/mvnw verify).spring-artifactory-gradle-snapshot.yml/spring-artifactory-maven-snapshot.yml— CI SNAPSHOT publish to Artifactory.spring-merge-dependabot-pr.yml— relabels Dependabot PRs (dependency-upgrade → task for dev-dependency groups), assigns the current milestone, and queues auto-merge; has special-case logic to reject Dependabot's incorrect-SNAPSHOT-skipping-GA upgrades (see the long comment in that file before touching this logic).spring-cherry-pick.yml— parsesAuto-cherry-pick to X.Y.x & A.B.xout of every commit message in a push (not just the head commit) and cherry-picks (-x) each matching commit to its named branches, stripping the trigger phrase from the picked commit message.spring-backport-issue.yml— fires on commits containingFixes:/Closes:, delegates tospring-io/backport-bot.spring-announce-milestone-planning.yml— posts to Google Chat when a milestone'sdue_onis set/changed.spring-trigger-dependabot-updates.yml— forces a Dependabot re-scan by toggling the executable bit ondependabot.yml.spring-build-and-deploy-docs.yml/spring-dispatch-docs-build.yml— Antora docs build + rsync publish + Cloudflare cache bust.merge-dependabot-pr.ymlandrelease-workflows.ymlunder.github/workflows/(not called externally) are this repository's own CI, not part of the reusable library.
Every reusable workflow declares its own secrets: under workflow_call — treat that block as the authoritative list of what a caller must pass (usually via secrets: inherit in a nested call, or explicitly from repo/org secrets at the top level). README.md lists the full set of org-level secrets consumer projects are expected to provision (GH_ACTIONS_REPO_TOKEN, JF_ARTIFACTORY_SPRING, ARTIFACTORY_USERNAME/PASSWORD, CENTRAL_TOKEN_*, GPG_*, DEVELOCITY_ACCESS_KEY, SPRING_RELEASE_CHAT_WEBHOOK_URL) — several are optional and gate optional steps via if: (e.g. signing only runs if GPG secrets are present, chat announce only if the webhook secret is present). When adding a new optional integration, follow this same "declare as required: false, guard the step with if:" pattern rather than making it mandatory.