Best Practices and Anti-Patterns
Topic 73

Common Anti-Patterns

Best Practices

Every rule in this course has a shadow: the specific way teams break it and the specific damage that follows. This closing page gathers those failures into one catalog so you can recognize a pattern before it bites — each entry paired with the consequence that makes it dangerous and the fix that neutralizes it.

Read this as the inverse of the preceding three pages. Where they stated the discipline, this one names the recurring violations across Git, collaboration, and Actions, grouped by where they live. The grouping matters less than the reflex it builds: see the shape, recall the cost, apply the fix.

Git History Anti-Patterns

These are the mistakes that corrupt the record itself — rewriting shared history, bloating the repo with things that never belonged, and letting branches drift until merges become ordeals. The common thread is treating history as scratch space rather than a durable, shared artifact.

Collaboration Anti-Patterns

These failures happen at the team boundary, where review and protection are supposed to catch problems. Giant unreadable PRs and an unprotected trunk both have the same effect: they make the process look rigorous while letting anything through.

Actions and Pipeline Anti-Patterns

Pipeline anti-patterns turn CI/CD from an asset into an attack surface or a source of false confidence. Mutable action tags and the pull_request_target trap are supply-chain risks; rebuilding artifacts per environment is a correctness risk that ships untested code to production.

Secrets and Security Anti-Patterns

The most expensive category, because the consequence is a compromise rather than an inconvenience. Committed secrets and long-lived cloud keys both create standing credentials with a wide blast radius; the fixes — scanning, rotation, and OIDC — remove the standing target entirely.

Common Mistakes
  • Committing secrets to history — the key is compromised the moment it is pushed and scraped within minutes; fix by enabling push protection and secret scanning, rotating immediately, and purging with git filter-repo.
  • git push --force on a shared branch — it overwrites teammates' commits you never fetched; fix with --force-with-lease and branch protection that blocks force-push to main.
  • Mutable action tags like @v4 — a hijacked tag runs attacker code with your tokens across every workflow; fix by pinning to full commit SHAs and bumping via Dependabot.
  • pull_request_target that checks out PR-head code — untrusted code runs with a write token and secrets, a public-repo remote code execution; fix by using pull_request and gating secret jobs behind Environment approvals.
  • Over-broad GITHUB_TOKEN permissions — a compromised step can push, tag, or publish; fix with permissions: {} plus the minimum each job needs.
  • Giant PRs of 1000+ lines — they get approved unread and bugs ship; fix by capping at about 400 lines and stacking smaller PRs.
  • Long-lived divergent feature branches — they cause merge-day conflict marathons; fix with short-lived branches rebased on current main and merged often.
  • Rebasing a pushed shared branch — collaborators re-merge the old history and create duplicate commits; fix by rebasing only local, unpushed work.
  • No branch protection on main — CI becomes advisory and anyone merges anything; fix by requiring checks and reviews and blocking force-push.
  • Committing build output, node_modules, or large binaries — the repo bloats permanently; fix with .gitignore, Git LFS, or external object storage.
  • Rebuilding artifacts per environment — production runs code that staging never tested; fix by building once and promoting the same digest.
  • Storing long-lived cloud keys as repository secrets — full blast radius if leaked; fix with OIDC federation scoped to the repository and environment.
Best Practices
  • Treat each anti-pattern's fix as the default: SHA-pin actions, least-privilege tokens, --force-with-lease, branch protection, build-once-promote, and OIDC.
  • Turn on GitHub push protection and secret scanning org-wide so committed secrets are blocked at push time.
  • Enforce small PRs and required reviews so the collaboration anti-patterns cannot merge in the first place.
  • Use pull_request for untrusted code and protected Environments for anything that bears a secret.
  • Make the safe path the easy path with templates, reusable workflows, and protected branches.
Comparable toolsNo direct equivalent — synthesis chapter the same anti-patterns recur everywhereGitLab push rules, protected branches, and ID tokens map the same fixesBitbucket merge checks and secret scanning cover the same failures

Knowledge Check

What is the specific consequence of committing a secret to history?

  • The key is compromised the moment it is pushed and scraped within minutes, so it must be rotated and purged from history, not just deleted later
  • The remote rejects every subsequent push until the committed secret is removed from the working tree, returning a blocked-push error on each attempt until the offending value is stripped and the branch is force-pushed clean
  • Nothing happens, as long as you delete the secret in the very next commit you push, since the follow-up commit overwrites the value everywhere it appeared in the branch
  • Only collaborators holding repository admin rights are ever able to read the committed value, so the exposure stays contained to a small trusted group with elevated access

Which fix correctly addresses force-pushing over a teammate's work?

  • --force-with-lease plus branch protection that blocks force-push to main
  • Always using plain --force but only after a fresh git fetch
  • Rebasing the shared branch onto the latest main right before pushing
  • Pushing more frequently so there is a smaller window of work to overwrite

Why is pull_request_target checking out PR-head code a security-critical mistake?

  • Untrusted PR code runs with a write token and access to secrets, enabling remote code execution on a public repo
  • It merely slows the whole pipeline down by forcing one extra and redundant source checkout step on every single run
  • It silently blocks fork pull requests from ever triggering any CI run at all on the repo
  • It is only a stylistic preference that carries no real security consequence whatsoever

Which anti-pattern causes production to run code that staging never tested?

  • Rebuilding artifacts per environment instead of building once and promoting the same digest
  • Caching downloaded dependencies keyed on the lockfile hash and reusing them between runs
  • Requiring a PR template with a manual testing checklist that every author completes before merge
  • Automatically deleting the feature branch once its pull request has been merged into main

Which of these is security-critical rather than merely annoying?

  • Storing long-lived cloud keys as repository secrets, which carry full blast radius if leaked
  • Writing a vague "WIP" commit message on a branch the whole team shares
  • Committing generated build output that permanently bloats the repository and slows every clone
  • Opening a pull request that runs a few hundred lines over the team's agreed line cap

You got correct