# 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.
**Red thread context:** the `facturation-api` project — 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 are joining the `facturation-api` team. The repository does not have a `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)

- REST API for invoicing in **Python 3.12 / FastAPI**, PostgreSQL base, ORM SQLAlchemy (ORM: Object-Relational Mapping, object-relational correspondence).
- Dependency management with **poetry** — *not* pip. Test command is `poetry run pytest`, linter is `poetry run ruff check .`, formatter `poetry run ruff format .`.
- The integration tests (`tests/integration/`) require a local PostgreSQL database launched by `docker compose up -d db`. Without it, they fail with misleading connection errors.
- The `app/migrations/` folder is generated by Alembic: **never edit it by hand** — any modification goes through `poetry run alembic revision --autogenerate`.
- Team convention: comments and docstrings **in French**, names of variables/functions **in English**.
- Amounts are **always** handled in cents (integers), never in floats. This is the source of the most expensive bug in the history of the project.
- The `app/config.py` file reads environment variables; locally, they come from `.env` (never committed).
- Branches: `main` protected, we are working on `feature/xxx`, mandatory PR, mandatory review.

### Your task

Write the complete `CLAUDE.md` file for the project.

**Constraints:**
1. The **4 canonical sections**: overview, conventions, common commands, known pitfalls (gotchas).
2. All orders must be **accurate and copyable** (as provided above).
3. At least **3 gotchas** specific to the project.
4. **Less than 100 lines.** Density is a rating criterion.
5. Bonus: a section “What Claude Code should never do” (explicit prohibitions).

**You can use the web page's CLAUDE.md builder as a scaffold — but the final version must be reworked by hand.**

### Evaluation criteria (/10)

| Criterion | Points |
|---|---|
| 4 canonical sections present and relevant | 4 |
| Exact, copyable commands | 2 |
| ≥ 3 specific gotchas (including cents and migrations) | 2 |
| Conciseness < 100 lines | 2 |

### Reflection question (to be written in 3 lines)

The prohibition “never edit `app/migrations/` by hand” appears in your `CLAUDE.md`. Is this **enough** 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)

### ContextThe 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 review 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:
1. Trigger on `pull_request` (opening and synchronization).
2. Check out the code **with enough history to calculate the PR diff**.
3. Install Claude Code on the runner. ⚠ *Check the current installation command in the official documentation — it is evolving.*
4. Execute Claude Code in **headless mode** (`claude -p "..."`) with a review instruction that requires: potential bugs, security issues, compliance with `CLAUDE.md` conventions, Markdown output.
5. Post the output as a comment to the PR (via `gh pr comment` or the GitHub API).
6. Be **non-blocking**: a failure of the review job must not cause the PR to fail (`continue-on-error` or equivalent).
7. Have a reasonable **timeout** (cost protection).

Starting skeleton (to be completed — the `# TODO` are your work):```yaml
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 review job file `.claude/settings.json`

Configure permissions **as 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`? of `Edit`? *(no — justify in a comment)*
- What variants of `Bash` are needed to read the PR diff?
- `WebFetch`: risk or necessity here?

#### Artefact C — Architectural note (½ page)

Answer:
1. Why does the review need to be **non-blocking** at launch? Under what conditions could you make it blocking later?
2. 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?
3. Estimate the **cost control** levers: timeout, diff size, prompt caching, trigger frequency. Propose a policy.

### Evaluation criteria (/15)

| Criterion | Points |
|---|---|
| Correct pattern, plausible and complete workflow | 4 |
| Triggers, non-blocking, timeout | 3 |
| `settings.json` least privilege, justified | 4 |
| Architecture note: injection + costs treated seriously | 4 |

**Critical fault:** an “allows everything” mode (`--dangerously-skip-permissions` or total 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 go to a **dedicated branch with PR**, never a direct push to `main`. Describe the permissions (hint: `Write` is needed — how do I limit it to the folder `tests/`? 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 `psql` with host `prod-db` should be blocked, even if a human approves it).
- **E2.** Any file modified by the agent must be immediately reformatted with `ruff format` (guarantee of style, 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 time-stamped **audit log** — regulatory auditability requirement.
- **E4.** When an agent session ends, a summary must be sent to the team's Slack channel (via an internal webhook, script `notify-slack.sh` already provided).

### Your task

#### Part 1 — Design table

For **each requirement R1–E4**, complete the table:

| Requirement | Hook event chosen (`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 **E1** hook script (bash or python, your choice). He must:
1.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*).
2. Only be interested in `Bash` calls.
3. Block if the command contains both `psql` and `prod-db` (be robust: case, spaces).
4. If stuck: exit with a failure code **and** emit 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)

1. **E1 could also be processed by a permission `deny`** (e.g. prohibit `Bash(psql:*)`). Compare the two approaches: what do we lose, what do we gain with the hook? When to choose one or the other?
2. **E3 in `PreToolCall` or `PostToolCall`?** The statement asks to log the *results* — what does this require? Can you need both?
3. A `PostToolCall` reformatting hook (E2) that **fails** (ruff crashes): what should happen? Should the session end? Justify your failure policy.
4. Management asks: “can we trust the model to respect E1 if we write it in ALL CAPS in CLAUDE.md? » Write the architect's answer in 3 sentences, with the words *probabilistic* and *deterministic*.

### Evaluation criteria (/10)

| Criterion | 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 (teacher's aide-memoire — do not distribute)

- **E1** → `PreToolCall`, blocking. Hook rather than permission if you want to authorize `psql` to the dev/staging databases (fine conditional logic, impossible with a simple deny pattern).
- **E2** → `PostToolCall` (filtered on `Edit`/`MultiEdit`/`Write`), non-blocking. Insufficient CLAUDE.md instruction: formatting must be *guaranteed*, not likely.
- **E3** → `PostToolCall` to capture the results (the `PreToolCall` does 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, calls `notify-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 *deterministic*: it executes every time, regardless of the state of the context. Regulatory requirements go in code, not text. »