Exercises — Session 6: Claude Code & CI/CD
Program : Applied AI — Advanced Level — Instructor: Yann Isola
Format: 3 exercises. Exercise 1 in session (20 min), Exercise 3 started in session (15 min), Exercise 2 scheduled in session and completed at home.
Context common thread: the project facturation-api — a REST API for invoicing in Python (FastAPI), with pytest tests, deployed on an internal cloud.
Exercise 1 — Write a CLAUDE.md (20 min, in session)
Context
You join the team facturation-api . The deposit has no CLAUDE.md : each developer who uses Claude Code repeats the same explanations in each session, and the agent always makes the same errors (bad test command, modifications prohibited in migrations/, comments in English while the team works in French).
Information about the project (extracted from an interview with the lead dev)
- Billing REST API Python 3.12 / FastAPI , PostgreSQL database, ORM SQLAlchemy (ORM: Object-Relational Mapping, object-relational correspondence).
- Managing dependencies with poetry — not pip. The test command is
poetry run pytest, the linter ispoetry run ruff check ., the trainerpoetry run ruff format .. - Integration tests (
tests/integration/) require a local PostgreSQL database launched bydocker compose up -d db. Without it, they fail with misleading connection errors. - The file
app/migrations/is generated by Alembic: never edit it by hand — any modification goes throughpoetry run alembic revision --autogenerate. - Team convention: comments and docstrings in French , variable/function names in English .
- The amounts are always handled in centimes (integers), never in floats. This is the source of the most costly bug in the history of the project.
- The file
app/config.pyreads environment variables; locally, they come from.env(never committed). - Branches:
mainprotected, we work onfeature/xxx, PR required, review required.
Your task
Write the file CLAUDE.md complete project.
Constraints:
- THE 4 canonical sections : overview, conventions, common commands, known pitfalls (gotchas).
- All orders must be accurate and copyable (as provided above).
- At least 3 gotchas specific to the project.
- Less than 100 lines. Density is a rating criterion.
- Bonus: a section “What Claude Code should never do” (explicit prohibitions).
You can use the constructor CLAUDE.md of the web page as scaffolding — but the final version must be reworked by hand.
Evaluation criteria (/10)
| Criteria | Points |
|---|---|
| 4 canonical sections present and relevant | 4 |
| Exact, copyable commands | 2 |
| ≥ 3 specific gotchas (including cents and migrations) | 2 |
| Concision < 100 lines | 2 |
Reflection question (to be written in 3 lines)
The “never edit” ban app/migrations/ by hand” appears in your CLAUDE.md . Is this sufficient to guarantee it? If not, what additional mechanism do you propose, and why? (Hint: think about the persuasion/ability/control divide seen in Part D.)
Exercise 2 — Set up a CI/CD pipeline with Claude Code (framed in session, completed at home)
Context
The lead dev of facturation-api wants to automate the PR review : each time a Pull Request is opened or updated, Claude Code must produce a code review posted as a comment. The magazine is non-blocking (it informs, it does not prevent the merge). The repository is hosted on GitHub, the CI is GitHub Actions.
Your task
Deliver three artifacts :
Artifact A — The GitHub Actions workflow (.github/workflows/claude-review.yml )
Write the complete workflow. He must:
- Trigger on
pull_request(opening and synchronization). - Check out the code with enough history to calculate the diff of PR.
- Install Claude Code on the runner. ⚠ Check the current install command in the official documentation — it's evolving.
- Execute Claude Code in headless mode (
claude -p "...") with a review instruction that requires: potential bugs, security issues, compliance with the conventions of theCLAUDE.md, output in Markdown. - Post the output as a comment on the PR (via
gh pr commentor the GitHub API). - Be non-blocking : a failure of the review job should not cause the PR to fail (
continue-on-erroror equivalent). - Have a timeout reasonable (cost protection).
Starting skeleton (to be completed — the # TODO are your job):
name: Revue Claude Code
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
continue-on-error: true
timeout-minutes: 10 # protection coûts / blocage
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # historique complet pour le diff
# TODO : installer Claude Code
# TODO : exécuter claude -p avec l'instruction de revue
# (clé API dans les secrets du dépôt : ANTHROPIC_API_KEY)
# TODO : poster la sortie en commentaire de PR
Artifact B — The file .claude/settings.json of the review job
Configure permissions strictly necessary for a read-only review. Reminder: in headless, no one clicks on “authorize” — everything must be decided in advance, and anything superfluous is a loophole.
Guiding questions:
- Does the journal need
Write? ofEdit? (no — justify in a comment) - What variations of
Bashare needed to read the PR diff? WebFetch: risk or necessity here?
Artefact C — Architectural note (½ page)
Answer:
- Why should the review be non-blocking at launch? Under what conditions could you make it blocking later?
- What is the risk of prompt injection in this pipeline (think: PR content is written by third parties) and how do your permissions mitigate it?
- Estimate the levers of cost control : timeout, diff size, prompt caching, trigger frequency. Propose a policy.
Evaluation criteria (/15)
| Criteria | Points |
|---|---|
| Correct pattern, plausible and complete workflow | 4 |
| Triggers, non-blocking, timeout | 3 |
settings.json at the least privilege, justified |
4 |
| Architectural note: injection + costs treated seriously | 4 |
Redhibitory fault: an “allow all” mode (--dangerously-skip-permissions or full permissions) in a runner with access to secrets → rating capped at 7/15. An architect doesn't do that.
Bonus expansion (+3)
Add a second job : generation of tests when coverage drops. Constraint: the generated tests start on a dedicated branch with PR , never a direct push on main . Describe the permissions (hint: Write is necessary — how to limit it to the file tests/ ? The proper answer involves Exercise 3…).
Exercise 3 — Design a hook system (15 min start in session, finish at home)
Context
facturation-api processes customer billing data. The Compliance team has four requirements before allowing Claude Code into the team:
- E1. No shell command should ever touch the production database (any command containing
psqlwith the hostprod-dbmust be blocked, even if a human approves it). - E2. Any file modified by the agent must be immediately reformatted with
ruff format(style guarantee, without depending on the goodwill of the model). - E3. Each action of the agent (each tool call: which tool, which parameters, which result) must be recorded in a audit log timestamped — regulatory auditability requirement.
- E4. When an agent session ends, a summary should be sent to the team's Slack channel (via an internal webhook, script
notify-slack.shalready provided).
Your task
Part 1 — Design table
For each requirement R1–E4, fill in the table:
| Requirement | Chosen hook event (PreToolCall / PostToolCall / Notification / Stop ) |
Blocking? | Script logic (pseudo-code, 3–6 lines) | Why NOT a simple instruction in CLAUDE.md ? |
|---|---|---|---|---|
| E1 | ||||
| E2 | ||||
| E3 | ||||
| E4 |
Part 2 — Write a Complete Hook
Write the hook script E1 (bash or python, your choice). He must:
- Receive information from the tool call (the tool called and its parameters — in practice provided in JSON on standard input ⚠ check the exact format in your version's doc ).
- Only interested in calls
Bash. - Block if the command contains both
psqlAndprod-db(be robust: breakage, spaces). - In case of blocking: exit with a failure code And send an explanatory message — the model will receive it and be able to adjust its strategy instead of stupidly trying again.
Part 3 — Architect Questions (3–5 lines each)
- E1 could also be handled by a permission
deny(e.g. prohibitBash(psql:*)). Compare the two approaches: what do we lose, what do we gain with the hook? When to choose one or the other? - E3 in
PreToolCallOrPostToolCall? The statement asks to log the results — what does this impose? Can you need both? - A hook
PostToolCallreformatting (E2) which failed (plant ruff): what should happen? Should the session end? Justify your failure policy. - Management asks: “can we trust the model to respect E1 if we write it in UPPER CASE in CLAUDE.md ? » Write the architect’s response in 3 sentences, with the words probabilistic And determinist .
Evaluation criteria (/10)
| Criteria | Points |
|---|---|
| Right event for every requirement, correct blocking | 4 |
| E1 script: robust, explanation message, exit code | 3 |
| Architect questions: persuasion/capacity/mastered control distinction | 3 |
Expected answer key (teacher’s cheat sheet — do not distribute)
- E1 →
PreToolCall, blocking. Hook rather than permission if you want to authorizepsqltowards the basics of dev/staging (fine conditional logic, impossible with a simple deny pattern). - E2 →
PostToolCall(filtered onEdit/MultiEdit/Write), non-blocking. Instruction CLAUDE.md insufficient: formatting must be guaranteed , not likely. - E3 →
PostToolCallto capture the results (thePreToolCalldoes not yet know the result); both if we also want to track blocked attempts. Non-blocking, but strict failure policy to discuss (an audit that no longer logs is a dead audit). - E4 →
Stop, non-blocking, callsnotify-slack.sh. - Question 4, standard answer: “No. A prompt instruction is probabilistic : the model almost always follows it, but “almost” is unacceptable for a conformance requirement. A hook is determinist : it executes every time, regardless of the state of the context. Regulatory requirements go in code, not text. »