This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This file captures only what isn't obvious from reading the files themselves — quirks, conventions, and gotchas. Don't restate the file tree or what each file plainly contains.
PostHog/.github is the org-level special repository: reusable workflows + composite actions referenced by every other repo in the org, org-wide community health file defaults, and the org profile (profile/README.md).
There is no build step and no app. Changes are config (YAML workflows, semgrep rules) plus a couple of Node scripts, but the blast radius is the whole org — hence .github/ and workflows/ are CODEOWNED by @PostHog/team-security and most changes get their review.
- The doubled path. Reusable workflows here are referenced as
PostHog/.github/.github/workflows/<name>.yml@main— the.github/.github/is correct, not a typo. workflow_callpreserves the original event. When a workflow here is invoked viaworkflow_callfrom another repo,github.event_namekeeps the original event (e.g.pull_request), notworkflow_call. This is an undocumented special case of the.githubrepo; several workflows branch on it. Read the comments inflags-project-board.ymlbefore "fixing" any event-name check.flags-boards.jsonis loaded at runtime, not baked into the workflow SHA — so editing the team→board map doesn't require callers to re-pin.
No general test suite. Run the changeset hygiene script tests with:
node --test .github/scripts/check-changeset-coverage.test.mjsThe semgrep rule tests run with:
semgrep --test .semgrep/Each rule has a paired .test.yaml fixture — update it when you touch a rule. Workflows themselves can't be unit-tested; reusable workflows expose a script-ref / ref input (default main) so you can point a caller at a branch of this repo while iterating against a real PR.
- Pin every action to a full commit SHA with a trailing
# vX.Y.Zcomment — third-party and first-partyPostHog/*. - Pin every container image to a digest —
name:tag@sha256:<digest>forcontainer:,services:, anddocker://refs. GitHub's SHA-pinning policy coversuses:action refs only and explicitly excludes registry images, so the customgithub-actions-unpinned-imagerule is the only enforcement. Resolve a digest withdocker buildx imagetools inspect <name>:<tag> --format '{{.Manifest.Digest}}'. - No shell injection. Never interpolate
${{ steps.*.outputs.* }}or other untrusted values into arun:/script:block. Route through anenv:var and reference"$VAR"double-quoted. The customgithub-actions-shell-injectionrule enforces this. pull_request_targetis effectively banned (github-actions-pull-request-targetrule errors on it). If truly required: don't check out the PR head, scopepermissions:minimally, and add a justified# nosemgrep:line reviewed by security.- Set explicit least-privilege
permissions:on every workflow/job. - Declare
timeout-minuteson every job. The GitHub default is 360 minutes, so a hung job holds a runner for six hours before anything kills it. Set a generous multiple of the job's normal runtime, not a tight bound.
When you change how this repo works in a way that contradicts or outdates the above — a new gotcha, a changed convention, a removed workflow that's referenced here — update this file in the same change. Keep it to non-obvious, file-listing-independent guidance; if something becomes plainly visible from the files, drop it from here rather than duplicating it.