# Add Github Workflows

> Add GitHub Actions workflows to a repo that has none, matching both the organisation's existing pipeline conventions and the repo's actual stack — Node steps for a frontend, Maven for a JVM service, and so on. Use when asked to "add the github flow/actions/CI" to a project, to bring a repo in line with its siblings' pipelines, or when a repo has no `.github/workflows`.

- Skill: `laasilva/add-github-workflows` (Agent Skill)
- Install (CLI): `npx skillmds@latest add laasilva/add-github-workflows`
- Raw SKILL.md: https://api.skillmd.com/api/skills/laasilva/add-github-workflows/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: laasilva (https://skillmd.com/u/laasilva)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/laasilva/add-github-workflows

---


# Add GitHub workflows to a repo

Give a repo the pipeline its siblings already have, translated to its own stack.

Two failure modes bracket this task. **Copying a sibling verbatim** gives a frontend a `mvn verify`
it cannot run. **Writing a generic pipeline from scratch** ignores the org's release conventions, so
the repo versions, tags, and deploys differently from everything around it. The job is to keep the
*shape* and replace the *steps*.

## Phase 1 — Read the org's existing pipeline

Never start from a blank file. Find the closest sibling repo that already has workflows:

```bash
ls ../*/.github/workflows/ 2>/dev/null
cat ../<sibling>/.github/workflows/*.yml
```

Extract the conventions and write them down before generating anything:

- **File split** — typically one workflow per trigger (`pr.yml` for validation, `dev.yml`/`main.yml`
  for release). Match the naming and the trigger branches.
- **Version scheme.** This is the highest-risk thing to get wrong because it is invisible from the
  code. In Dracolich the bump is parsed from the **commit subject**: `feature*` → major,
  `update*` → minor, `fix*` → patch, anything else → **no release at all**. Reproduce the logic
  exactly, including the `should_release` short-circuit, or this repo will version out of step with
  every sibling.
- **Job chain and its order** — what depends on what, and which steps are irreversible.
- **Artifact destinations** — package registry, image registry and tag format, and any downstream
  repo that gets written to.
- **Secrets used**, by name.
- **Permissions blocks** per workflow.

## Phase 2 — Detect the repo's real stack

Read the manifest; don't assume from the repo name.

```bash
ls package.json pom.xml build.gradle* pyproject.toml go.mod Cargo.toml *.csproj 2>/dev/null
```

Then determine, from files rather than convention:

- **Runtime version** — `engines.node` / `.nvmrc` / `<java.version>` / `python-version` in a config.
  Pin the same version the project actually targets.
- **Which scripts genuinely exist.** Read `scripts` in `package.json`, or the build file's tasks.
  **Never invent a step for a script that isn't there** — a `npm test` step in a repo with no test
  runner fails the pipeline on its first run and teaches everyone to ignore red builds.
- **Whether tests exist at all.** Zero test files means no test step, and say so in your report
  rather than silently omitting it.
- **Lockfile presence** — `npm ci` requires `package-lock.json`; without one it must be `npm install`.
- **What the deployable artifact is.** This is the fork that decides the whole release half of the
  pipeline (see Phase 4).

## Phase 3 — Map the stages

Keep the sibling's job chain; swap each stage's implementation.

| Stage | JVM / Maven | Node / TS | Python | Go | .NET |
|---|---|---|---|---|---|
| Toolchain | `actions/setup-java` + distribution | `actions/setup-node` + `cache: npm` | `actions/setup-python` | `actions/setup-go` | `actions/setup-dotnet` |
| Install | (implicit) | `npm ci` | `pip install -e .[dev]` / `poetry install` | `go mod download` | `dotnet restore` |
| Registry auth | `settings.xml` for GitHub Packages | `.npmrc` — **only if it consumes/publishes private packages** | index URL | `GOPRIVATE` | nuget source |
| Lint | (often none) | `npm run lint` | `ruff` / `flake8` | `go vet`, `golangci-lint` | `dotnet format --verify-no-changes` |
| Type check | (compiler) | `npx tsc --noEmit` | `mypy` | (compiler) | (compiler) |
| Test | `mvn -B test` | `npm test` *(only if it exists)* | `pytest` | `go test ./...` | `dotnet test` |
| Build | `mvn -B package` | `npm run build` / framework export | `python -m build` | `go build` | `dotnet publish` |
| Publish | `mvn deploy` | `npm publish` | `twine` | release binaries | `dotnet nuget push` |

Drop stages that don't apply rather than finding something to put there. A JVM repo's
GitHub-Packages `settings.xml` block has no Node equivalent unless the project actually consumes
private npm packages — check before adding an `.npmrc` step.

## Phase 4 — Decide what the release half produces

For a backend service the answer is usually "a container image, then bump the chart". For a
frontend or app it genuinely forks, and **you must establish which before writing the release
workflow** — ask if the repo doesn't settle it:

- **Static web bundle → container** — build (`npm run build`, `expo export --platform web`, `vite
  build`), copy into an nginx/static image, push, bump the chart. Mirrors the backend pipeline.
- **Static web bundle → static host** — Pages, S3/CloudFront, Netlify. No image, no chart bump.
- **Mobile app → store** — a different pipeline entirely (EAS Build/Submit, Fastlane, signing
  credentials, store API keys). Do not fold this into the same job as a web build; the triggers,
  secrets, and cadence all differ.
- **Library** — publish to a registry; no image at all.

An Expo repo can be two of these at once (web bundle to the cluster, native builds to stores). If
so, that is two workflows, not one with branches.

Also note: **env vars baked at build time** (`EXPO_PUBLIC_*`, `VITE_*`, `NEXT_PUBLIC_*`) must be
present in the build job, not the runtime container. Missing them produces a silently misconfigured
bundle rather than a failure.

## Phase 5 — Write the workflows

Start from the sibling file, edit it down, and keep unchanged everything that isn't stack-specific:
trigger branches, `permissions`, concurrency, the version-bump script, job dependencies, and the
downstream bump job.

Validate before reporting done:

```bash
python3 -c "import yaml,sys;[yaml.safe_load(open(f)) for f in sys.argv[1:]];print('YAML OK')" \
  .github/workflows/*.yml
command -v actionlint >/dev/null && actionlint .github/workflows/*.yml
```

Then read each `run:` line and ask whether that command would succeed in a clean checkout of *this*
repo. Most breakage is a command that doesn't exist here, not malformed YAML.

## Phase 6 — Secrets and first-run safety

List every secret the workflows reference and check it exists at repo or org level. The user must
add any that don't — you cannot, and you should never put a secret value in a workflow file.

**Order matters more than it looks.** In a chain like
`tag → publish-package → publish-image → bump-chart`, the tag and GitHub Release are pushed *before*
the later jobs run. A missing registry secret therefore leaves a tag and a release with no artifact
behind them — a half-release someone has to clean up by hand. Before the first run on the default
branch, either confirm every secret is present, or trigger it once via `workflow_dispatch` on a
branch where the irreversible steps are skipped.

Say explicitly in your report that the pipeline is **unverified until it runs**, and offer to watch
the first execution.

## Calibration

- **Match the org's conventions even when you'd design it differently.** Consistency across sibling
  repos is worth more than your preferred layout. Note improvements; don't apply them uninvited.
- **No step without a command that exists.** Check `scripts`, check the test directory, check the
  lockfile.
- **Don't add a deploy step you can't ground.** If you don't know where the artifact goes, ask.
  Guessing produces a pipeline that pushes something somewhere nobody intended.
- **Don't run the pipeline to "test" it.** Pushing to a release branch triggers a real release. Use
  YAML validation, `actionlint`, and reading — and let the user decide when to run it.
- **Pin action versions** to the major the siblings use, so all repos update together.

