Skip to content

Instantly share code, notes, and snippets.

@donbr
Last active September 23, 2025 18:02
Show Gist options
  • Save donbr/d68c5d6fbec48c165111c32b63be88f2 to your computer and use it in GitHub Desktop.
Save donbr/d68c5d6fbec48c165111c32b63be88f2 to your computer and use it in GitHub Desktop.
claude-code-ci-cd-processes.md

Research Prompt (paste into Claude)

Role & Goal You are an expert DevEx engineer researching best-practice change-management and task-management workflows using Claude Code (CLI & SDK) in real engineering teams as of Sept 22, 2025. Produce actionable guidance I can adopt in a repo that already uses a YAML task plan and a Prefect 3 flow to orchestrate phases/tasks 1:1 (think .claude/tasks.yamlflows/golden_testset_flow.py).

What to cover (prioritize authoritative sources):

  1. MCP configuration & scopes — current, documented best practice for using project-scoped .mcp.json in VCS vs user-scoped/global config; precedence with .claude/settings.json and managed policy files; environment-variable expansion and approval prompts for project MCP. Cite docs.
  2. Claude Code settings for governance — permission model (allow/ask/deny), enabling/approving .mcp.json servers, “includeCoAuthoredBy” in commits, relevant env vars (MCP_TIMEOUT, MAX_MCP_OUTPUT_TOKENS), and how Desktop Extensions factor in (install/approval flows, import from Desktop). Cite docs.
  3. Task management patterns — representing work as versioned tasks/phases in a YAML plan that a flow maps 1:1 (pre/post checks, quality gates, perf budgets, artifacts), and driving CI with that plan. Provide examples that align with typical tasks.yaml → Prefect execution and GitHub Actions matrix.
  4. Change-management controls — GitHub/GitLab branch protection, CODEOWNERS, required reviews, required status checks, rulesets, PR templates, Conventional Commits, auto-merge policies, and how they interplay with Claude Code’s Git tools and CI. Cite official docs.
  5. Security & safetyMCP trust boundaries, prompt-injection risks for 3rd-party servers, secret handling (env/managed policy), permission prompts, and least-privilege patterns. Cite docs or reputable advisories.
  6. Operator UX — daily commands (claude mcp add/list/get/remove, /mcp), importing Desktop MCP, debugging failures, and practical examples for ticket systems (Linear/Jira), docs (Notion), and error monitoring (Sentry).

Deliverables:

  • A 1-page set of recommendations (≤12 bullets) with inline citations to Anthropic Claude Docs, Model Context Protocol (modelcontextprotocol.io), and GitHub/GitLab docs. Prefer official docs over blogs; include dates where possible.
  • A step-by-step change-management process (feature branch → PR → status checks → merge) showing exactly where Claude Code and MCP are used.
  • A task-management template (minimal tasks.yaml slice) showing phases/tasks with run/verify/perf_budget_ms/artifacts and how CI maps matrix to phases.
  • Minimal .mcp.json (project-scoped) + .claude/settings.json examples for permissions and server approvals, with notes on approval prompts and precedence.
  • A PR checklist purpose-built for Claude-assisted changes (permissions evidence, MCP server list, status checks, artifacts).

Style constraints:

  • Be concise; use bullet points, small code blocks; no marketing copy.
  • Cite 5–8 “load-bearing” facts with links and dates (e.g., MCP spec revs, Anthropic docs pages, GitHub branch-protection pages).
  • Prefer examples that will run in CI and on macOS/WSL2.

Repo assumptions to align with:

  • .claude/tasks.yaml is the source of truth for phases/tasks; CI fans out across phases; a Prefect 3 flow maps YAML → flows/tasks 1:1 and enforces perf budgets & artifacts.
  • CI has development vs enterprise modes; status checks must pass.

Output: Provide the recommendations, process, templates, and checklist. Then include a short “why these practices” note.


Findings (what the research will confirm)

  • Use project-scoped MCP via .mcp.json in the repo (checked into VCS) when collaborating; Claude Code documents the structure, scope flags, precedence, and env-var expansion for commands/args/headers/urls. (Claude Docs)
  • Settings precedence today: enterprise managed policy → CLI args → .claude/settings.local.json.claude/settings.json~/.claude/settings.json; use permissions.{allow,ask,deny} and options like enableAllProjectMcpServers, includeCoAuthoredBy. (Claude Docs)
  • Desktop Extensions (Claude Desktop) provide one-click MCP install and you can import Desktop MCP servers into Claude Code via claude mcp add-from-claude-desktop. (Anthropic)
  • MCP is JSON-RPC-based; hosts (Claude Code/Desktop) connect to servers exposing tools/resources/prompts; official spec + architecture explain participants & transports (stdio/SSE/HTTP). (Model Context Protocol)
  • Branch protection + CODEOWNERS remain the canonical way to enforce PR review & checks before merge. Use “Require review from Code Owners”, required approvals, and required status checks. (GitHub Docs)
  • Your repo’s pattern already matches best practice: a flow reads tasks.yaml and maps phases/tasks 1:1; CI runs a matrix over phases and consumes .claude/tasks.yaml.

Recommended change-management process (Claude Code + MCP)

  1. Plan & open branch

    • Use Claude Code with MCP servers for your tracker (e.g., Linear/Jira) to pull acceptance criteria and generate a task slice for this change. Keep work on a feature branch; main is protected. (Claude Docs)
  2. Author changes with permissions on

    • In .claude/settings.json, deny secret reads; ask for risky Bash; enable includeCoAuthoredBy for transparency. (Claude Docs)
  3. Encode tasks in .claude/tasks.yaml for repeatability

    • Add/modify a phase task (run/verify/perf_budget_ms/artifacts). The Prefect 3 flow will enforce perf budgets and run verifies.
  4. Local rehearsal via flow

    • python flows/golden_testset_flow.py --only-phase <phase> (dev mode) and fix failures before PR.
  5. Open PR

    • PR template requires: updated tasks, artifacts attached, status checks passing, links to Claude Code session notes (/mcp output), and MCP server list from .mcp.json.
  6. CI runs

    • Matrix executes each phase using the YAML; required status checks (lint, security, perf, integration) must pass.
  7. Review gates

    • CODEOWNERS + protected branches enforce owner review; rules require PR approvals & passing checks before merge. (GitHub Docs)
  8. Merge & label

    • Use Conventional Commits; CI tags release and archives phase reports.
  9. Post-merge

    • Dashboards (Phoenix/metrics) and artifacts provide evidence; incidents create follow-up tasks.

Task-management template (minimal “slice”)

# .claude/tasks.yaml  (slice)
schema: "ai-tasks/v0.3"
version: "2.2"
project: "rag-eval-foundations"
phases:
  - id: phaseX
    name: "Implement <feature>"
    depends_on: [phase1]
    tasks:
      - id: px.impl
        name: "Code & unit tests"
        perf_budget_ms: 180000   # 3 minutes
        run:
          - "ruff check src/ --fix"
          - "pytest -q"
        verify:
          - "python scripts/verify_quality_gates.py --all"
        artifacts:
          - "reports/unit-test-report.xml"

CI should fan out by phase in a matrix and call the flow with --only-phase ${{ matrix.phase }}; your current workflow already does this against .claude/tasks.yaml.


Minimal project MCP & settings

.mcp.json (project-scoped, VCS-checked): (approved structure & env expansion per Claude Docs) (Claude Docs)

{
  "mcpServers": {
    "linear": { "type": "sse", "url": "https://mcp.linear.app/sse", "headers": { "Authorization": "Bearer ${LINEAR_API_KEY}" } },
    "notion": { "type": "http", "url": "https://mcp.notion.com/mcp", "headers": { "Authorization": "Bearer ${NOTION_API_KEY}" } },
    "sentry": { "type": "http", "url": "https://mcp.sentry.dev/mcp" }
  }
}

.claude/settings.json (project-shared): (Claude Docs)

{
  "enableAllProjectMcpServers": true,
  "includeCoAuthoredBy": true,
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Bash(curl:*)"],
    "ask": ["Bash(git push:*)", "Bash(pnpm i:*)"],
    "allow": ["Bash(npx ruff:*)", "Bash(pytest:*)"]
  },
  "env": {
    "MCP_TIMEOUT": "15000",
    "MAX_MCP_OUTPUT_TOKENS": "50000"
  }
}

Add or import servers via CLI (claude mcp add …, claude mcp add-from-claude-desktop) and inspect with /mcp in the REPL. (Claude Docs)


PR checklist (Claude-assisted changes)

  • Task plan updated (.claude/tasks.yaml) with run/verify/perf_budget_ms/artifacts.
  • Local flow run passed for target phase(s) (--only-phase).
  • CI status checks passing (lint, security, performance, integration).
  • MCP inventory included (servers from .mcp.json + justification/least privilege). (Claude Docs)
  • Permissions evidence: paste /config relevant snippets; secrets not exposed; .claude/settings.json denies sensitive file reads. (Claude Docs)
  • Branch protection & CODEOWNERS approvals satisfied; required reviews met. (GitHub Docs)
  • Artifacts attached (reports/logs) and links to Phoenix/metrics dashboards.
  • Conventional Commit message + PR description includes rationale & risk.

Risks & quick mitigations

  • 3rd-party MCP servers can expand attack surface (prompt-injection, data exfil). Use project scope only for vetted servers; prefer OAuth where available; keep dangerous tools in ask/deny. (Claude Docs)
  • Overscoped permissions in Claude Code. Enforce deny-by-default on secrets; enable enterprise managed policy if available. (Claude Docs)
  • Flaky governance without branch protection. Require status checks and CODEOWNERS reviews on main. (GitHub Docs)

Why this fits your repo right now

  • Your flow parses YAML and runs phases/tasks 1:1, enforcing perf budgets and artifacts; CI already executes phases from .claude/tasks.yaml — the templates above plug straight in.

If you want, I can drop these snippets into PR-ready files (.mcp.json, .claude/settings.json, and a minimal .github/PULL_REQUEST_TEMPLATE.md) next.

Done! I created a small, PR-ready package you can drop into your repo.

What’s included

  • .mcp.json — project-scoped MCP servers (Linear, Notion, local DeepAgents example)
  • .claude/settings.json — safe defaults (deny secrets, ask for risky commands, allow test/lint)
  • .claude/tasks.yaml — starter slice aligned to your Prefect-3 flow + CI fan-out
  • .github/PULL_REQUEST_TEMPLATE.md — checklist tailored for Claude-assisted changes
  • .github/CODEOWNERS.sample — placeholders for your teams

Download

  • Download the ZIP

If you want, I can also tailor .mcp.json to your actual MCP servers (Tavily, Brave, etc.) and pin the PR template to your exact required checks from ci.yaml.

Short answer: yes—those are real Ruff findings, and they line up with its flake8/pycodestyle, isort, pyupgrade, pep8-naming, and whitespace rule sets. Here’s the mapping + quickest path to green.

What the errors mean (by family)

  • E501 line too long → pycodestyle’s max line length (Ruff defaults to 88). Wrap, split args, or raise the limit. (Astral Docs)
  • I001 unsorted imports → Ruff’s isort rule; run the import sorter. (Astral Docs)
  • UP006/UP045/UP017/UP035/UP043 → pyupgrade rules (use list/dict/X | None, prefer datetime.UTC, drop deprecated typing). Auto-fixable (some marked “unsafe” if you depend on runtime typing). (Astral Docs)
  • N806 variable should be lowercase → pep8-naming (only allow UPPERCASE for constants).
  • F401/F841 unused imports/vars → flake8-F family (actually Ruff’s built-in “F”).
  • W29x/W29y/W291/W293 newline/whitespace cleanups → end-of-file newline, trailing spaces, etc.

One-shot fixer (safe defaults)

Run these locally or in CI; they’ll clear ~90% of the list:

# 1) Sort imports + format
ruff check . --select I --fix
ruff format .

# 2) Apply safe pyupgrade/classic fixes
ruff check . --select F,E,W,N,UP --fix

# 3) If you’re on Python ≥3.11 and don’t use runtime type inspection (e.g., old Pydantic v1),
#    you can allow "unsafe" auto-fixes (speeds up UP006/UP045 churn):
ruff check . --select UP --fix --unsafe-fixes

Then re-run ruff check . to verify zero findings.

Minimal .ruff.toml tuned for this repo

This matches your stack (Prefect 3, modern typing) and reduces noise in tests:

# .ruff.toml
target-version = "py311"
line-length = 100  # or 120 if you prefer

[lint]
select = [
  "F","E","W",      # flake8 core
  "I",              # isort
  "UP",             # pyupgrade
  "N",              # pep8-naming
]
ignore = [
  # example: allow long strings in migration files
  # "E501",
]

[lint.per-file-ignores]
# tests often have long parametrizations and literals
"tests/**" = ["E501", "N806"]
# if needed for notebooks/experiments
"src/langchain_eval_experiments.py" = ["E501","W291","W293"]

[format]
quote-style = "preserve"
indent-style = "space"

[lint.pyupgrade]
keep-runtime-typing = false  # set true if you rely on runtime typing libs

[lint.isort]
known-first-party = ["src","golden_testset"]
combine-as-imports = true

Tip: if you keep line-length=88, expect many E501s until you wrap calls. Bumping to 100/120 is common in RAG/Prefect codebases with long SQL/arg lists.

Hotspots you listed & exact fixes

  • I001 (several files):

    ruff check . --select I --fix && ruff format .
  • UP006: change from typing import List, Dict, Tuple → use builtin generics: List[str]list[str], Dict[str, Any]dict[str, Any], Tuple[int, str]tuple[int, str]. (Astral Docs)

  • UP045: Optional[X]X | None.

  • UP017: from datetime import UTC; dt.replace(tzinfo=UTC) instead of timezone.utc. (Python 3.11+).

  • N806 (in functions): change POSTGRES_USER et al to postgres_user or move them to module-level constants if they’re intended as constants.

  • F401/F841: drop unused imports/vars or prefix with _ if kept for structure.

  • E501: wrap function args, break f-strings, or raise line-length as above.

  • W292: ensure newline at EOF:

    git ls-files -z | xargs -0 -I{} sh -c 'tail -c1 "{}" | read -r _ || echo >> "{}"'
  • W291/W293: run ruff format . to strip trailing spaces / bad blank lines.

Make it stick in CI

Add a fast pre-flight to your workflow before the heavy matrix:

# .github/workflows/ci.yaml
jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/ruff-action@v3
        with:
          args: "check ."
  # existing jobs depend on ruff:
  validate:
    needs: [ruff]
    ...

If you want auto-fix PRs on push (common with long E501/UP churn), add a separate job that runs on branches and commits fixes via a bot.

About “similar posts” to Anthropic’s best-practices

Good sources to skim alongside the course:

  • Anthropic engineering post: Claude Code: Best practices for agentic coding (repo structure, CLAUDE.md patterns, task planning). (Anthropic)
  • Anthropic learning hub (“Build with Claude”) for RAG, evals, extended thinking (helps shape your CLAUDE.md and CI prompts). (Anthropic)
  • DeepLearning.AI short course: “Claude Code — A Highly Agentic Coding Assistant” (hands-on with MCP, context control, CLAUDE.md). (DeepLearning.ai)
  • Community/industry write-ups on AI code reviews in GitHub (context + automation patterns). (GitHub)

If you want, I can generate a one-time PR that (1) drops the .ruff.toml above, (2) runs import-sort/format/pyupgrade over src/ and flows/, and (3) bumps your CI with a pre-flight ruff job + summaries.

# Create PR-ready files for Claude Code governance and MCP setup
import os, json, textwrap, zipfile, pathlib
base_dir = "/mnt/data/claude_pr_package"
claude_dir = os.path.join(base_dir, ".claude")
github_dir = os.path.join(base_dir, ".github")
os.makedirs(claude_dir, exist_ok=True)
os.makedirs(github_dir, exist_ok=True)
# 1) .mcp.json (project-scoped)
mcp_json = {
"mcpServers": {
"linear": {
"type": "sse",
"url": "https://mcp.linear.app/sse",
"headers": {"Authorization": "Bearer ${LINEAR_API_KEY}"}
},
"notion": {
"type": "http",
"url": "https://mcp.notion.com/mcp",
"headers": {"Authorization": "Bearer ${NOTION_API_KEY}"}
},
"local-deepagents": {
"command": "uv",
"args": ["run", "python", "integrations/mcp-rag/minimal_test.py"],
"cwd": "integrations/mcp-rag",
"timeout": 120000
}
}
}
with open(os.path.join(base_dir, ".mcp.json"), "w", encoding="utf-8") as f:
json.dump(mcp_json, f, indent=2)
# 2) .claude/settings.json (project-shared)
settings_json = {
"enableAllProjectMcpServers": True,
"includeCoAuthoredBy": True,
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Bash(curl:*)"],
"ask": ["Bash(git push:*)", "Bash(pip install:*)", "Bash(uv run:*)"],
"allow": ["Bash(ruff *)", "Bash(pytest *)", "Bash(python flows/*)"]
},
"env": {
"MCP_TIMEOUT": "15000",
"MAX_MCP_OUTPUT_TOKENS": "50000"
}
}
with open(os.path.join(claude_dir, "settings.json"), "w", encoding="utf-8") as f:
json.dump(settings_json, f, indent=2)
# 3) .claude/tasks.yaml (starter slice)
tasks_yaml = textwrap.dedent("""\
schema: "ai-tasks/v0.3"
version: "2.2"
project: "rag-eval-foundations"
description: "Starter tasks slice to validate governance with Claude Code + Prefect 3"
phases:
- id: phase.research
name: "Research & Synthesis"
tasks:
- id: collect.sources
name: "Collect authoritative sources"
run:
- "mkdir -p docs/research"
- "python - << 'PY'\nfrom pathlib import Path; Path('docs/research/sources.md').write_text('# Sources\\n', encoding='utf-8')\nPY"
verify:
- "test -f docs/research/sources.md"
artifacts:
- "docs/research/sources.md"
- id: phase.impl
name: "Implement change"
depends_on: [phase.research]
tasks:
- id: impl.code_and_tests
name: "Code & unit tests"
perf_budget_ms: 180000
run:
- "ruff check src/ --fix"
- "pytest -q"
verify:
- "python scripts/verify_quality_gates.py --all || echo 'skip if script absent'"
artifacts:
- "reports/unit-test-report.xml"
- id: phase.flow-validation
name: "Validate Prefect flow by phase"
depends_on: [phase.impl]
tasks:
- id: validate.flow
name: "Run flow for this phase"
run:
- "uv run python flows/golden_testset_flow.py --only-phase phase.impl || python flows/golden_testset_flow.py --only-phase phase.impl"
verify:
- "echo 'Flow completed (see CI logs for details)'"
artifacts: []
""")
with open(os.path.join(claude_dir, "tasks.yaml"), "w", encoding="utf-8") as f:
f.write(tasks_yaml)
# 4) .github/PULL_REQUEST_TEMPLATE.md
pr_tpl = textwrap.dedent("""\
## Summary
<!-- What change does this PR introduce? Why? Link to issue/ticket if applicable. -->
## How tested
- [ ] Local flow run: `uv run python flows/golden_testset_flow.py --only-phase <phase>` (or `python ...`)
- [ ] Unit tests pass locally (`pytest -q`)
- [ ] Linting (`ruff check src/ --fix`)
## Checklist (Claude-assisted changes)
- [ ] **Task plan updated**: `.claude/tasks.yaml` includes `run/verify/perf_budget_ms/artifacts` for this change.
- [ ] **MCP inventory**: project `.mcp.json` lists only the servers required for this PR; least-privilege headers/tokens used.
- [ ] **Permissions evidence**: key snippets from `.claude/settings.json` (deny/ask/allow) are appropriate; no secret file reads.
- [ ] **CI status checks** passing (lint, security, performance, integration).
- [ ] **CODEOWNERS/branch protection** approvals satisfied.
- [ ] **Artifacts** (reports/logs) attached where relevant.
- [ ] **Conventional Commit** style in title (e.g., `feat:`, `fix:`, `docs:`).
## Notes for reviewers
<!-- Risks, trade-offs, or follow-ups -->
""")
with open(os.path.join(github_dir, "PULL_REQUEST_TEMPLATE.md"), "w", encoding="utf-8") as f:
f.write(pr_tpl)
# 5) Optional: sample CODEOWNERS (with placeholders)
codeowners = textwrap.dedent("""\
# Learn more: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# Replace placeholders with your GitHub team or user handles.
* @your-org/eng-core
flows/ @your-org/data-eng
.claude/ @your-org/devex
""")
with open(os.path.join(github_dir, "CODEOWNERS.sample"), "w", encoding="utf-8") as f:
f.write(codeowners)
# Zip up the package for easy download
zip_path = "/mnt/data/claude-pr-package_20250922.zip"
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z:
for root, _, files in os.walk(base_dir):
for file in files:
full = os.path.join(root, file)
rel = os.path.relpath(full, base_dir)
z.write(full, arcname=os.path.join("claude_pr_package", rel))
# Return a directory listing for visibility
file_tree = []
for p in pathlib.Path(base_dir).rglob("*"):
file_tree.append(str(p.relative_to(base_dir)))
file_tree, zip_path
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment