{"owner":"telepresenceio","repo":"telepresence","hasSkills":true,"totalSkillsCount":10,"totalTokensCount":11200,"categories":["subagent-persona","anthropic-skill","root-instruction"],"hasMcp":false,"mcpConfig":null,"found":[".claude/agents/proto-rpc-reviewer.md",".claude/skills/changelog-entry/SKILL.md",".claude/skills/changelog-entry/agents/openai.yaml",".claude/skills/prepare-release/SKILL.md",".claude/skills/prepare-release/agents/openai.yaml",".claude/skills/regression-tests/SKILL.md",".claude/skills/regression-tests/agents/openai.yaml",".claude/skills/ship-release/SKILL.md",".claude/skills/ship-release/agents/openai.yaml","AGENTS.md"],"skills":{".claude/agents/proto-rpc-reviewer.md":"---\nname: proto-rpc-reviewer\ndescription: Use when reviewing changes to any .proto file under rpc/ or to the Go bindings generated from them. Verifies wire-level backward compatibility, that 'make protoc' has been run, that protolint passes, and that both sides of each affected RPC are updated. Surfaces incompatibilities that would break older clients, older traffic-managers, or older traffic-agents talking to a new peer.\ntools: Read, Grep, Glob, Bash\n---\n\nYou are the gRPC contract reviewer for the telepresence repository.\n\n## Communication boundaries you must consider\n\nThe repo defines four RPC surfaces; a single proto edit can ripple across them:\n\n| Boundary                               | Proto package        |\n|----------------------------------------|----------------------|\n| client/userd ↔ traffic-manager         | `rpc/manager/`       |\n| client ↔ user daemon                   | `rpc/connector/`     |\n| client ↔ root daemon                   | `rpc/daemon/`        |\n| traffic-manager ↔ traffic-agent        | `rpc/agent/`         |\n| auth                                   | `rpc/authenticator/` |\n| teleroute (docker network driver)      | `rpc/teleroute/`     |\n| shared types                           | `rpc/common/`        |\n\nEach daemon ships independently: an older client may talk to a newer traffic-manager, a newer traffic-manager may inject an older traffic-agent (mismatched manifest), and a newer agent may run alongside an older sidecar in another pod. Wire compatibility is therefore mandatory, not optional.\n\n## Checks you must run\n\n1. **Wire compatibility:**\n   - Field numbers must never be reused or repurposed.\n   - Field types must not change (e.g., int32 → int64 silently corrupts).\n   - Enum values must not be renumbered; only appended.\n   - `optional` and `repeated` are part of the wire contract; do not flip.\n   - Removing a field requires `reserved` to lock the number/name.\n2. **Generated code is in sync:** Confirm `make protoc` has been run — check that .pb.go files in the same package are touched in the same change. If not, flag and recommend running it.\n3. **Lint:** Confirm `protolint` would pass against the configured rules in `.protolint.yaml` (line length 120, ENUM_FIELD_NAMES_PREFIX disabled). Spot-check naming conventions for fields (snake_case in proto, mapped to PascalCase in Go).\n4. **Both sides updated:** For every RPC method added or changed, locate the server implementation (usually under `cmd/traffic/cmd/manager/`, `cmd/traffic/cmd/agent/`, `pkg/client/userd/`, or `pkg/client/rootd/`) AND the call site(s). If only one side is touched, flag it.\n5. **Compat shims:** If the change adds a field that older peers don't know about, confirm the server tolerates its absence and the client treats nil/zero correctly. Reject any change that requires a synchronized upgrade of both sides.\n\n## Reporting format\n\nReturn a punch list, not prose. For each finding:\n\n- **Severity:** Blocker / Risk / Nit\n- **Where:** file:line\n- **Why:** one sentence\n- **Fix:** one sentence\n\nEnd with a one-line verdict: \"Safe to merge\", \"Needs follow-up\", or \"Blocked\".\n\n## What NOT to do\n\n- Do not edit any files. You are a reviewer.\n- Do not run `make protoc` yourself; report whether it appears to have been run and let the caller decide.\n- Do not chase code-style nits unrelated to the proto/RPC contract.\n",".claude/skills/changelog-entry/SKILL.md":"---\nname: changelog-entry\ndescription: Add a new entry to CHANGELOG.yml under the current unreleased version (or create the version block if needed), then regenerate documentation. Use when the user says things like \"add a changelog entry\", \"log this fix in the changelog\", or \"/changelog-entry\".\n---\n\n# changelog-entry\n\nAdds an entry to `CHANGELOG.yml` following the schema documented in the file header, then regenerates the derived documentation.\n\n## Inputs to gather (in order)\n\n1. **type** — one of `bugfix`, `feature`, `security`, `change`. If the user describes the change but does not pick a type, infer it:\n   - \"fixes/resolves/closes a bug\" → `bugfix`\n   - \"adds support for / introduces / new\" → `feature`\n   - \"CVE / vulnerability / hardens\" → `security`\n   - anything else affecting behavior → `change`\n2. **title** — short (≤80 chars), sentence-cased, no trailing period.\n3. **body** — 2-3 sentences. **This field is HTML, not markdown.** Use `<code>...</code>` for code, `<a href=\"...\">...</a>` for links. Prefer YAML's `>-` folded scalar so line wrapping doesn't leak literal newlines.\n4. **docs** *(optional)* — path to a docs page under `docs/` if the entry deserves a \"Learn more\" link.\n5. **image** *(optional)* — path under the `release-notes` directory if there's a visual.\n\n## Steps\n\n1. Read the top of `CHANGELOG.yml`. The first `items:` entry is the current/upcoming version.\n2. If it has `date: (TBD)`, append the new entry to its `notes:` array.\n3. If the top item is already dated (a shipped release), insert a NEW `- version: <next>` block above it with `date: (TBD)` and the single new note. Ask the user for the next version number — do not invent it.\n4. Match existing indentation exactly (2 spaces). YAML is whitespace-sensitive.\n5. After saving, run `make docs-files` to regenerate `docs/release-notes.md`, `docs/release-notes.mdx`, and `docs/variables.yml`. (The PostToolUse hook in `.claude/settings.json` will also try to do this; running it explicitly here makes the success/failure visible.)\n6. Show the user the diff: `git diff CHANGELOG.yml docs/release-notes.md docs/release-notes.mdx docs/variables.yml`.\n\n## Schema reference (from CHANGELOG.yml header)\n\n```yaml\nitems:\n  - version: 2.28.0\n    date: (TBD)              # or YYYY-MM-DD\n    notes:\n      - type: bugfix         # bugfix | feature | security | change\n        title: Short title\n        body: >-\n          Two or three sentences describing the change and why it\n          is noteworthy.  This is HTML.\n        docs: optional/path\n        image: optional/path\n```\n\n## Things to avoid\n\n- Do not edit `docs/release-notes.md`, `docs/release-notes.mdx`, or `docs/variables.yml` directly — they are generated.\n- Do not include markdown syntax in `body`; it is rendered as HTML.\n- Do not set `date:` to a concrete date for upcoming versions; `make prepare-release` does that automatically for GA versions.\n",".claude/skills/changelog-entry/agents/openai.yaml":"policy:\n  allow_implicit_invocation: true\n",".claude/skills/prepare-release/SKILL.md":"---\nname: prepare-release\ndescription: Create the local release commit and tags by setting TELEPRESENCE_VERSION and running make prepare-release. Stops at the local commit+tags - pushing is the ship-release skill's job. Use when the user explicitly asks to prepare a release, RC, or test build. User-only.\ndisable-model-invocation: true\n---\n\n# prepare-release\n\nWraps the `make prepare-release` step so the local-tag-creation portion of a release is one explicit user action, not a chain of remembered commands. Stops at local tags; the `ship-release` skill takes over from there.\n\nThis is **user-only** by design (`disable-model-invocation: true`). The tags this skill creates will eventually drive a public release, so creating them must be an explicit user decision — never a side-effect of Claude inferring intent.\n\n## Confirm before doing anything\n\nAsk the user explicitly:\n\n1. **Version string** (`TELEPRESENCE_VERSION`) — must be one of:\n   - `vX.Y.Z-test.N` — pre-release, no Homebrew, no \"latest\"\n   - `vX.Y.Z-rc.N` — pre-release, no Homebrew, no \"latest\"\n   - `vX.Y.Z` — GA, marked latest, triggers Homebrew update\n2. **Branch** — should be a release branch (typically `release/v2`). Refuse to proceed from `main`-style branches.\n3. **Working tree** — must be clean. Run `git status`; if there are uncommitted or untracked files relevant to the build, stop.\n4. **CHANGELOG.yml status** — the top entry should have version matching `TELEPRESENCE_VERSION` (without the leading `v`). If it's still `date: (TBD)`, that's expected: `make prepare-release` sets the date for GA versions.\n\nShow all four checks to the user before running anything. Wait for explicit \"go\".\n\n## Steps\n\n```bash\nexport TELEPRESENCE_VERSION=vX.Y.Z[-suffix.N]\nmake prepare-release\n```\n\nThis creates:\n- An annotated tag `vX.Y.Z[-suffix.N]`\n- An annotated tag `rpc/vX.Y.Z[-suffix.N]`\n- A commit that bumps go.mod references inside the repo\n\nVerify with:\n\n```bash\ngit log -1 --stat\ngit tag --points-at HEAD\n```\n\n## Next: hand off to `ship-release`\n\nThis skill stops here, with the local commit and the two annotated tags. **Do not push anything.** To carry the release through CI, the docs PR, the Releases workflow, and the PR merges, invoke the `ship-release` skill (after pushing the branch and opening a PR on it — that's a manual handoff step the user does between the two skills).\n\n## Refuse to\n\n- Push anything (branch, commit, or tags). That's `ship-release`'s job.\n- Skip `make prepare-release` and just tag manually. The make target updates go.mod references; manual tagging skips that and ships a broken module.\n- Re-run `make prepare-release` on top of a previous attempt without first cleaning up the leftover tags. If `git tag --points-at HEAD` already lists the target tag, stop and report — the user has to decide whether to delete it.\n",".claude/skills/prepare-release/agents/openai.yaml":"policy:\n  allow_implicit_invocation: false\n",".claude/skills/regression-tests/SKILL.md":"---\nname: regression-tests\ndescription: Run, scope, or debug telepresence regression tests under regression_test/ — the integration-level suite. Use when the user wants to run an area, suite, or single test, debug a failure, or says \"/regression-tests\". Runs `go test ./regression_test` scoped with -run, in the background, writing to a log file so heavy output stays out of context.\n---\n\n# regression-tests\n\nRuns the telepresence regression suite from the main conversation, where this\nharness's shell-env quirks are known.\n\n## Background to assume\n\n- Tests live under `regression_test/` and need a working k8s cluster (kind /\n  minikube / Docker Desktop) plus images it can reach. For a local cluster set\n  `RTEST_REGISTRY=local` and LOAD the images into it rather than pushing.\n- `regression_test/README.md` is the reference: fixture-engine rules, the\n  RTEST_* table, catalogs, labels, coverage. Read it before debugging a\n  fixture problem.\n- **The shell environment always wins; there is no config file.** That means a\n  stale `export` in the user's shell silently changes a run.\n\n## Scoping: plain `go test -run`\n\nAreas are ordinary Go tests, suites and methods are subtests, so one `-run`\nexpression selects at any depth:\n\n```\ngo test ./regression_test -run '^TestIntercept$'\ngo test ./regression_test -run '^TestIntercept$/^HeaderFilter$'\ngo test ./regression_test -run '^TestIntercept$/^HeaderFilter$/^Test_PathPrefix$'\n```\n\nThere is no `TEST_SUITE`/`TEST_NAME` indirection and no make-argument dance:\ninline `VAR=value` prefixes work in this harness, so run `go test` directly\nand keep `make check-regression` for the full unscoped suite.\n\nAlways pass `-count=1` (results must never come from the test cache) and a\n`-timeout` that fits the scope: minutes for one suite, `-timeout=100m` for a\nfull run.\n\n## The run command\n\n```\nTELEPRESENCE_REGISTRY=local TELEPRESENCE_VERSION=<version> \\\n  RTEST_CONTEXT=<context> RTEST_TEARDOWN=1 \\\n  go test -count=1 -timeout=30m -run '^TestArea$/^Suite$' ./regression_test \\\n  > /tmp/rtest-suite.log 2>&1\n```\n\n- `RTEST_CONTEXT` pins the kube context. Pin it explicitly whenever the\n  machine has more than one cluster — the default is the kubeconfig's current\n  context, which is one `kubectx` away from being the wrong cluster.\n- `RTEST_TEARDOWN=1` destroys the run's resources at the end. Without it, dev\n  mode keeps namespaces, the release, and the connection for the next run to\n  adopt, which is what makes a scoped rerun take seconds.\n- `RTEST_FRESH=1` ignores adoptable resources (use when a previous run left\n  something suspect). CI implies fresh + teardown.\n- `RTEST_LABELS` / `RTEST_SKIP_LABELS` select on `compat-core`, `slow`,\n  `stress`, `flaky-retry`.\n\n## CRITICAL: a stale TELEPRESENCE_VERSION silently poisons the build\n\nThe version under test is read from the binary itself, and `make build` stamps\nit from `TELEPRESENCE_VERSION`. If the user's shell exports an old value, the\nbinary gets that version, the manager image tag no longer matches, and the run\nfails at image pull with a version that appears nowhere in your command.\n\nAlways pass `TELEPRESENCE_VERSION` explicitly to BOTH `make build` and the\n`go test` invocation, with the same value.\n\n## Rebuild before running\n\nThe suite runs the prebuilt binary plus the cluster-side images, so rebuild\nwhatever changed:\n\n- client-side Go (`pkg/`, `cmd/telepresence`) **and any `charts/` change**:\n  `make build` — the chart is go:embedded in the client binary, so a\n  chart-only edit without a rebuild silently installs the OLD chart.\n- manager / agent (`cmd/traffic`, `charts/`): `make load-images` (or\n  `make load-tel2-image`), so the cluster gets the new image.\n- `--docker` tests: `make client-image` — the daemon container runs on the\n  workstation, so it only needs to exist locally.\n\n`RTEST_REGISTRY=local` makes the manager use `pullPolicy=Never`. Reserve\n`make push-images` and a real registry for a remote cluster.\n\n## Keep heavy output out of context\n\nThis runs in the main thread, so do NOT Read or `tail` the whole log.\n\n1. Launch with `run_in_background: true`, redirecting to a fresh path.\n2. On completion, read only the summary:\n   `grep -E 'passed,|^ok|^--- FAIL|^FAIL' /tmp/rtest-suite.log | tail -5`\n   The runner's own last line is\n   `[rtest] run <stamp>: N passed, N failed, N skipped (N fixture actions)`.\n3. For a failure, the per-test artifacts are under\n   `build-output/rtest/logs/<stamp>/<TestPath>/` (cli.log, daemon logs), and\n   `manifest.json` records every test's outcome and labels.\n\n## Leftover state\n\n- `make rtest-clean` removes everything the framework created, in the cluster\n  and locally.\n- A daemon from a killed run: `telepresence quit -s`.\n- One rtest run per cluster at a time — resource names are stable by design,\n  which is what makes adoption work.\n- A leftover manager from unrelated work (\"traffic-manager in namespace X\n  already manages namespace Y\") blocks an unrestricted install: find it with\n  `kubectl get secret -A -l owner=helm`, and remember the chart also leaves\n  cluster-scoped resources (webhook config, ClusterRole/Binding) that\n  namespace deletion does not remove.\n\n## Workflow\n\n1. **Identify** the area/suite: areas are directories under\n   `regression_test/suites/`, and each suite is an `rt.Suite` type registered\n   in its `init()`.\n2. **Decide the rebuild** (see above) and run it with an explicit\n   `TELEPRESENCE_VERSION`.\n3. **Run scoped**, in the background, to a fresh log path.\n4. **Summarize:** the command, pass/fail/skip counts, failing test names, the\n   smallest excerpt explaining each failure, and the next concrete action.\n\n## Don't\n\n- Don't run the full suite unscoped without explicit user instruction — it is\n  roughly an hour serial; `make check-regression SHARD=1|2|3` runs a third of\n  it (the shard/area mapping lives in build-aux/main.mk).\n- Don't run `go test -list` or a deliberately non-matching `-run` to \"check\n  what exists\": the harness provisions real cluster resources before selection,\n  so it costs a full setup cycle. Grep the suite files instead.\n- Don't `make clobber` or destroy local images without asking.\n- Don't edit generated files: `docs/reference/cli/**`, `DEPENDENCIES.md`,\n  `DEPENDENCY_LICENSES.md`, `docs/release-notes*`.\n",".claude/skills/regression-tests/agents/openai.yaml":"policy:\n  allow_implicit_invocation: true\n",".claude/skills/ship-release/SKILL.md":"---\nname: ship-release\ndescription: Drive a Telepresence release from a prepared branch all the way through CI, docs, the Releases workflow, and PR merges. Assumes `make prepare-release` has already been run locally and the branch with that commit was pushed and a PR opened. Use when the user says \"ship the release\", or \"complete the release\". User-only.\ndisable-model-invocation: true\n---\n\n# ship-release\n\nEnd-to-end driver for releasing Telepresence. Picks up where `prepare-release` left off and carries the change through:\n\n1. Telepresence release PR (CI green + `regression` green)\n2. Docs PR in `../telepresence.io`\n3. Tag push, Releases workflow, merge of both PRs.\n\nThis is **user-only** (`disable-model-invocation: true`). A release is publicly visible and partially irreversible (tags push to GitHub, Homebrew updates for GA). Claude must never invoke this on its own.\n\n## Preconditions to verify before doing anything\n\nRun each check and stop with a clear message if any fails:\n\n1. **CWD is the telepresence repo.** `git rev-parse --show-toplevel` ends in `telepresence`.\n2. **`make prepare-release` has been run.** The current HEAD must carry both `vX.Y.Z` and `rpc/vX.Y.Z` annotated tags locally:\n   ```\n   git tag --points-at HEAD | sort\n   ```\n   Two entries expected. If the tag list is empty or missing the `rpc/` peer, stop — the user needs to run `make prepare-release` first.\n3. **Branch is pushed.** Capture `tp_branch=$(git branch --show-current)`, then:\n   ```\n   git ls-remote --exit-code origin \"refs/heads/$tp_branch\"\n   ```\n   If this fails, stop and tell the user to push the branch first.\n4. **PR exists.** `gh pr view \"$tp_branch\" --json number,state,url,headRefName`. If no PR, stop.\n5. **Sibling docs repo present.** `test -d ../telepresence.io && test -d ../telepresence.io/.git`. If not, stop.\n\nCapture once and reuse throughout:\n\n- `tp_branch` — name of the prepare-release branch.\n- `tp_version` — pick the non-`rpc/` tag from `git tag --points-at HEAD` (e.g. `v2.28.0`).\n- `docs_version` — `echo \"$tp_version\" | sed -E 's/^v([0-9]+\\.[0-9]+).*/\\1/'` (e.g. `2.28`).\n- `pr_number` — from `gh pr view`.\n\n## Phase 1 — Drive the Telepresence release PR\n\n### 1.1 Verify branch and PR (already done in preconditions)\n\n### 1.2 Wait for all checks except `regression` to be green\n\nUse:\n\n```\ngh pr checks \"$tp_branch\" --json name,state,conclusion\n```\n\nFilter out the rows whose name is exactly `regression` or `node_agent_docker_runtime` (neither has been triggered yet — the label triggers them). Match the name exactly: `regression_compat` is a different job, and it only runs behind the `compatibility test` label. For every remaining row:\n\n- `state == \"COMPLETED\"` and `conclusion == \"SUCCESS\"` → green\n- `conclusion ∈ {\"FAILURE\",\"CANCELLED\",\"TIMED_OUT\",\"ACTION_REQUIRED\"}` → **stop**. Report the failing check name and a short excerpt from `gh run view <run-id> --log-failed`. Do not advance.\n- Anything else (`IN_PROGRESS`, `QUEUED`, `PENDING`) → keep waiting.\n\n**Polling cadence:** these checks (lint, unit tests, license, image-scan) typically finish in 5-15 min. Use `ScheduleWakeup` with `delaySeconds=180` while any check is still running. Do not tight-loop with sleeps.\n\n### 1.3 Wait for `regression` to be green\n\n`regression` starts automatically with the push (the release branch lives in\nthis repository); there is nothing to trigger. If it needs another attempt,\nuse \"Re-run all jobs\" on its workflow run (`gh run rerun <run-id>`).\n\nThe regression suite runs as three parallel shards (~30 min including cluster setup), summed into the single `regression` context; the node-agent job runs beside them. Use `ScheduleWakeup` with `delaySeconds` around **900**. Poll with the same `gh pr checks` query, looking at the `regression` and `node_agent_docker_runtime` rows.\n\n- Success → continue to Phase 2.\n- Failure / cancellation → **stop and report**. Pull failed-step logs with `gh run view <run-id> --log-failed`.\n- Still running after ~90 minutes → tell the user and stop (workflow may be stuck).\n\n## Phase 2 — Create the docs PR\n\nDone in the sibling repo `../telepresence.io`. Each shell step below is a separate Bash call (no `&&` chains), and `cd` to switch repos.\n\n### 2.1 Pull master\n\n```\ncd ../telepresence.io\ngit checkout master\ngit pull\n```\n\n### 2.2 Create branch with the same name as the telepresence PR branch\n\n```\ngit checkout -b \"$tp_branch\"\n```\n\nIf that branch already exists locally from a previous attempt, stop and ask whether to reuse, reset, or rename.\n\n### 2.3 + 2.4 Export variables\n\n```\nexport DOCS_VERSION=\"$docs_version\"   # e.g. 2.28 — note: no patch number\nexport DOCS_BRANCH=\"$tp_branch\"\n```\n\n(Per CLAUDE.md, `export` in its own Bash call, then use in subsequent calls. Shell state persists between calls in a session.)\n\n### 2.5 Generate\n\n```\nmake generate-version\n```\n\n### 2.6 Verify output\n\n```\nls versioned_docs/version-\"$DOCS_VERSION\"\ngit status\n```\n\nExpectations:\n\n- **Minor release** (first time this `2.X` is generated): the directory `versioned_docs/version-$DOCS_VERSION/` appears as untracked.\n- **Bugfix release** (directory already existed): files within it are modified.\n\nIf `versioned_docs/version-$DOCS_VERSION` is absent or `git status` shows no changes, **stop and report** — `make generate-version` did not do anything useful.\n\n### 2.7 Build the site locally before pushing\n\nNetlify (the `deploy/netlify`, `Pages changed`, `Header rules`, `Redirect rules`\nchecks) and the `Check`/`Lint` GitHub jobs all run `yarn build` (docusaurus\nbuild). Run it locally first so a broken build is caught here, not after a\npush-and-wait CI cycle:\n\n```\n# If node_modules is absent: yarn install --frozen-lockfile\nyarn build\n```\n\n- Exit 0 → the production build (including the new `version-$DOCS_VERSION`)\n  compiled. Proceed to the PR.\n- Non-zero → **stop and do not push.** Read the error; it names the offending\n  file and line.\n\n**Most common failure: MDX parse error in `release-notes.mdx`.** A `.mdx` file\nis JSX, so literal `{` / `}` **inside an HTML element** like\n`<code>{cmd, stdout}</code>` are parsed as JS expressions and fail with\n`Could not parse expression with acorn`. (Braces inside Markdown backtick\nspans — `` `{tcp|udp}` `` — are safe.) These release-notes files are generated,\nso fix the **source**, not the generated copy:\n\n1. In the telepresence repo, edit the offending `CHANGELOG.yml` entry to remove\n   the literal braces (rephrase, e.g. `<code>cmd</code>/<code>stdout</code>`, or\n   move the snippet into a backtick span), then `make docs-files`.\n2. Commit + push that fix to the release branch (it belongs in the release PR).\n3. Back in the docs repo, re-run `make generate-version` to re-pull the fixed\n   docs, then `yarn build` again before continuing.\n\n### 2.8 Create the PR\n\n```\ngit add versioned_docs/version-\"$DOCS_VERSION\" versioned_sidebars docusaurus.config.js versions.json\n# (Add only the files that actually changed — git status will tell you which of the\n#  above moved; for a fresh minor you'll likely see all of them, for a bugfix only some.)\ngit commit -s -S -m \"Generate docs for telepresence $tp_version\"\ngit push -u origin \"$tp_branch\"\ngh pr create --base master --head \"$tp_branch\" \\\n  --title \"Generate docs for telepresence $tp_version\" \\\n  --body \"Generated with \\`make generate-version\\` DOCS_VERSION=$docs_version DOCS_BRANCH=$tp_branch.\"\n```\n\nCapture `docs_pr_number` from the `gh pr create` output URL.\n\nDo not include \"Co-Authored-By\" or \"Generated with\" trailers in the commit message or the PR body (per global preferences).\n\n### 2.9 Monitor the docs PR checks\n\n```\ngh pr checks \"$tp_branch\" --json name,state,conclusion\n```\n\nSame polling rules as Phase 1.2. If anything fails, **stop and report**.\n\n## Phase 3 — Release\n\n`cd` back to the telepresence repo for step 3.1 and 3.3a.\n\n### 3.1 Push the release tags\n\n```\ngit push origin \"$tp_version\" \"rpc/$tp_version\"\n```\n\nThis triggers the **Releases** workflow (`.github/workflows/release.yaml`). The release PR is still unmerged at this point — that is intentional. Merging now would create a new commit and move the branch tip away from the tagged commit.\n\n### 3.2 Monitor the Releases workflow\n\n```\ngh run list --workflow=release.yaml --limit 1 --json databaseId,status,conclusion,url\ngh run view <id> --json jobs\n```\n\nThe workflow requires manual approval of a protected GitHub environment (`macos-signing`) containing secrets for macOS signing. Wait for this up to **24 hours**. Poll with `ScheduleWakeup` at `delaySeconds=1800` (or longer when overnight). Surface the workflow URL early so the user can chase the approver.\n\n- If the workflow completes successfully → continue to 3.3.\n- If a job other than `build-macos-pkg` fails → **stop and report**.\n- If `build-macos-pkg` itself is never approved within 24h → tell the user; per CLAUDE.md the release still ships without `.pkg` installers, and the user can decide whether to proceed to 3.3 anyway.\n\n### 3.3 Merge both PRs — **GA versions only**\n\n**For pre-release versions (`-test.N`, `-rc.N`): skip this step entirely and\nstop here.** Both PRs stay open until the GA release ships: the release\nbranch accumulates the rc and GA prepare-release commits and merges once,\nafter GA, and the docs PR must not publish the new version's docs on\ntelepresence.io before GA exists (regenerate it from the GA branch before\nmerging). The rc's GitHub pre-release and its tags are the only public\nartifacts of a pre-release ship.\n\nFor a GA version: order does not matter. Both must use a **merge commit**\n(CLAUDE.md: never squash, never rebase).\n\n```\n# telepresence PR (in telepresence repo)\ngh pr merge \"$tp_branch\" --merge\n\n# docs PR (in ../telepresence.io)\ncd ../telepresence.io\ngh pr merge \"$tp_branch\" --merge\n```\n\nVerify each merged: `gh pr view \"$tp_branch\" --json state` should report `MERGED`.\n\n## Long-wait strategy\n\n- Anything under 5 min → don't sleep; just poll once.\n- 5-30 min waits (Phase 1.2 non-regression checks) → `ScheduleWakeup` with `delaySeconds=180`.\n- 30-60 min waits (Phase 1.3 `regression`) → `ScheduleWakeup` with `delaySeconds=1200`.\n- Hours-to-overnight (Phase 3.2 macOS signing approval) → `ScheduleWakeup` with `delaySeconds=1800` or longer.\n\nEach wake-up: re-fetch state, decide green/red/still-waiting, schedule the next wake or advance.\n\n## What \"stop and report\" means\n\n- Do not advance to the next numbered step.\n- Surface: the step that failed, the check/run name(s), the run URL(s), and a short excerpt from `gh run view <id> --log-failed`.\n- Do not retry automatically. Wait for the user to direct.\n- Do not delete branches, force-push, or close PRs. The user decides what to do.\n\n## What this skill must NEVER do\n\n- Run `make prepare-release` itself — that's a separate skill and a separate decision.\n- Push tags before all required PR checks are green (Phase 1 must complete first).\n- Merge the release PR or the docs PR for a pre-release (`-test.*`/`-rc.*`) version — both stay open until GA (see 3.3).\n- Merge PRs as squash or rebase — both repos require merge commits.\n- Trigger `regression` by any means other than the push itself or a re-run of its workflow run.\n- Approve the `macos-signing` environment programmatically — that requires a human reviewer.\n- Force-push or delete the release branch.\n",".claude/skills/ship-release/agents/openai.yaml":"policy:\n  allow_implicit_invocation: false\n","AGENTS.md":"# AGENTS.md\n\nThis file provides guidance for contributors and AI assistants working with this repository.\n\n## Project Overview\n\nTelepresence is a Kubernetes development tool that enables fast local development by connecting your local workstation to a Kubernetes cluster. It allows developers to run services locally while accessing cluster resources and intercepting traffic from the cluster to their local machine.\n\n## Git Workflow\n\n- Never commit directly to the `release/v2` branch. Always create a feature branch with a name following the pattern `username/topic` (e.g., `thallgren/fix-dns-resolution`).\n- All commits must be signed and signed-off (`git commit -s -S`).\n- Limit commit message subjects to 72 characters. Do not wrap subjects;\n  rewrite them shorter instead. Wrap commit message body lines at 72\n  characters by default unless preserving exact external text requires a\n  longer line.\n- **Always run `make lint` and fix every reported issue before pushing.** This is non-negotiable — CI runs the same linters and a push with lint errors wastes a CI cycle. If `make lint` finds problems, fix them in the appropriate commit (use `git commit --fixup=<sha>` followed by `GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash --gpg-sign <base>` to fold them in) before pushing.\n- Push the branch and create a pull request for review.\n- Always merge PRs with a merge commit (never squash or rebase).\n\n## Design Plans\n\nMajor work (multi-file changes, new features, refactors) starts with a written plan\nunder `docs/plans/<topic>/`, presented for review before implementation begins.\n\nA plan is scaffolding for review, not a lasting artifact. It is removed in the last\ncommit on the PR that implements it. By then, everything in the plan must have been\nimplemented and documented, so the plan no longer has a purpose.\n\n## Build Artifacts\n\nThe Open Source version of Telepresence consists of three artifacts:\n\n**Client-side (runs on developer workstation):**\n- **`telepresence` binary** - The same binary serves as CLI, user daemon, and root daemon.\n- **`telepresence` Docker image** - Used as both user and root daemon when running `telepresence connect --docker`.\n\n**Cluster-side (runs in Kubernetes):**\n- **`tel2` Docker image** - Used by the traffic-manager deployment and injected as traffic-agent sidecars.\n\n## Build Commands\n\n```bash\n# Set required environment variables\nexport TELEPRESENCE_VERSION=v2.x.x-alpha.0  # or use auto-generated version\nexport TELEPRESENCE_REGISTRY=local          # 'local' for Docker Desktop, or 'ghcr.io/telepresenceio'\n\n# Build the telepresence binary\nmake build\n\n# Build Docker images (for local Kubernetes development)\nmake client-image    # Client container image\nmake tel2-image      # Traffic-manager/traffic-agent image\n\n# Build everything for local development\nmake build client-image tel2-image\n\n# Install to system\nmake install\n\n# Clean build artifacts\nmake clean\nmake clobber  # Also removes tools\n```\n\nEnvironment variables:\n- `TELEPRESENCE_REGISTRY` (required) - Docker registry for images. Use `local` for docker-based Kubernetes, or `ghcr.io/telepresenceio` for the release registry.\n- `TELEPRESENCE_VERSION` (optional) - Version string to compile into binaries and images. If not set, auto-generated from CHANGELOG.yml and source hash.\n\nRun `make help` for more information.\n\n### Building on Windows\n\nWindows builds use `build-aux\\winmake.bat` instead of `make` directly. Pass the same parameters as you would to make. The script runs make inside a Docker container with appropriate parameters for Windows binaries.\n\n## Testing\n\n```bash\n# Unit tests\nmake check-unit\n\n# Regression tests (requires a Kubernetes cluster; see the guide below)\nmake check-regression\n\n# One regression area / suite / test — plain go test selection:\ngo test ./regression_test -run 'TestIntercept/HeaderFilter/Test_PathPrefix'\n\n# Chart-value combinations, clusterless:\ngo test ./regression_test/golden\n```\n\nThe regression suite in `regression_test/` is the integration-test\npackage: declarative memoized fixtures, warm-cluster adoption for fast\nscoped runs, coverage instrumentation, and a bidirectional\ncompatibility subset. **Read `regression_test/README.md` before writing or\ndebugging these tests** — it documents the fixture engine's rules (lazy\naccessors, Mutate discipline, spec declarations), the RTEST_* environment,\nthe manager/workload catalogs, labels and platform constraints, coverage,\nand compat runs.\n\n## Linting\n\n```bash\n# Run all linters\nmake lint\n\n# Run Go linter only\nmake lint-go\n\n# Run protobuf linter only\nmake lint-rpc\n\n# Run documentation linter only (link/nav consistency via tools/src/docslint,\n# terminology and stale references via Vale in Docker; config in .vale.ini)\nmake lint-docs\n\n# Auto-fix lint issues\nmake format\n```\n\nLinting uses golangci-lint v2 running in Docker. Configuration is in `.golangci.yml`.\n\n## Code Comments\n\nComments must describe the code as it is. Never write comments that describe a\ntransition — why code was moved, what it replaced, or how it differs from an\nearlier version. The reader sees only the current code, so such comments carry\nno information for them. Keep comments short; avoid long explanations.\n\nOn internal (unexported) functions and methods, keep doc comments minimal: a\nfew lines stating only what the code cannot show, such as a locking-order or\npublication-order invariant. With well-named code, the details live in the\ncode itself; a reader who wants them will read it. Multi-paragraph comments\nthat justify design decisions belong in review discussions, not in the\nsource.\n\n## Code Generation\n\n```bash\n# Regenerate protobuf and license files\nmake generate\n\n# Regenerate protobuf files only\nmake protoc\n\n# Regenerate documentation files (after changing CHANGELOG.yml)\nmake docs-files\n```\n\n**Important:** After modifying `CHANGELOG.yml`, always run `make docs-files` to regenerate documentation files (`docs/release-notes.md`, `docs/release-notes.mdx`, `docs/variables.yml`).\n\n**Important:** All files under `docs/reference/cli/` are generated from Go source code. Do not edit them directly; instead, modify the corresponding Go source and regenerate.\n\n### Updating License Documentation\n\nRun `make generate` and commit changes to `DEPENDENCY_LICENSES.md` and `DEPENDENCIES.md`.\n\n## Documentation\n\nThe documentation under `docs/` aims to follow the\n[Diátaxis](https://diataxis.fr/) framework. Its four quadrants map to the\nlayout like this:\n\n| Diátaxis quadrant | Orientation | Location |\n|-------------------|-------------|----------|\n| Tutorials | learning | `docs/quick-start.md` |\n| How-to guides | task | `docs/howtos/` |\n| Reference | information | `docs/reference/` |\n| Explanation | understanding | `docs/concepts/` |\n\nWhen documenting a new feature, decide which quadrants it needs — typically a\nhow-to guide (how to enable/use it) plus a reference page (its complete\nbehavior, configuration, and limitations) — and keep the quadrants separate:\na how-to gets a task done and links to the reference for details; a reference\ndescribes exhaustively and doesn't teach. Add new pages to the navigation in\n`docs/doc-links.yml`, and run `make lint-docs` (link/nav consistency and\nterminology) before pushing.\n\n## Architecture\n\n### Main Components\n\n1. **CLI/Client** (`cmd/telepresence/`, `pkg/client/cli/`)\n   - Single binary serving as CLI, user daemon, and root daemon\n   - Commands are in `pkg/client/cli/cmd/`\n\n2. **User Daemon (userd)** (`pkg/client/userd/`)\n   - Runs as the user, manages connection to traffic-manager\n   - Handles intercepts, port forwards, cluster communication\n\n3. **Root Daemon (rootd)** (`pkg/client/rootd/`)\n   - Runs with elevated privileges\n   - Manages virtual network interface (VIF) and DNS\n\n4. **Traffic Manager** (`cmd/traffic/cmd/manager/`)\n   - Runs in the Kubernetes cluster (ambassador namespace by default)\n   - Coordinates intercepts between clients and traffic-agents\n\n5. **Traffic Agent** (`cmd/traffic/cmd/agent/`)\n   - Injected as sidecar into intercepted pods\n   - Routes traffic between the pod and the local machine\n\n6. **Agent Init** (`cmd/traffic/cmd/agentinit/`)\n   - Init container for setting up iptables rules in pods\n\n7. **Docker Network Driver** (`cmd/teleroute/`)\n   - Only used when connecting with `--docker` flag\n   - Provides the Docker network that enables communication between the Telepresence daemon container and other containers\n\n### Key Packages\n\n- `pkg/vif/` - Virtual network interface implementation\n- `pkg/tunnel/` - gRPC-based tunneling for network traffic\n- `pkg/dnsproxy/` - DNS resolution and proxying\n- `pkg/agentconfig/` - Traffic-agent configuration\n- `pkg/client/k8s/` - Kubernetes client interactions\n- `pkg/routing/` - Network routing logic\n- `pkg/client/cli/cmd/` - CLI commands. One per file.\n\n### RPC Definitions\n\nProtocol buffers are in `rpc/` with separate packages:\n- `rpc/connector/` - Client-to-userd communication\n- `rpc/daemon/` - Client-to-rootd communication\n- `rpc/manager/` - Client/userd-to-traffic-manager communication\n- `rpc/agent/` - Traffic-manager-to-traffic-agent communication\n\n### Version Parity Between CLI and Daemons\n\nThe CLI never talks to a user or root daemon of a different version.\n`pkg/client/cli/connect/version_check.go` enforces this on every command\nthat reaches a daemon: the host user daemon and root daemon must match the\nclient version exactly, and a containerized user daemon must match on\nmajor.minor.patch. This means changes to `rpc/connector/` and `rpc/daemon/`\nnever need backward-compatibility fallbacks — a new RPC can be assumed to\nexist on the daemon side. Backward compatibility DOES matter for\n`rpc/manager/` and `rpc/agent/`, where the cluster side is upgraded\nindependently of the client.\n\n### Helm Chart\n\nThe traffic-manager Helm chart is in `charts/telepresence-oss/`.\n\n## Debugging and Troubleshooting\n\n### Log Files\n\nThere are three log files:\n- `connector.log` - Output from user daemon: traffic-manager interaction, intercepts, port forwards\n- `daemon.log` - Output from root daemon: networking changes on your workstation\n- `cli.log` - Output from the command line interface\n\nLocations:\n- macOS: `~/Library/Logs/telepresence/`\n- Linux: `~/.cache/telepresence/logs/`\n- Windows: `%USERPROFILE%\\AppData\\Local\\logs`\n\nLogs rotate daily. Use `tail -F <filename>` to watch rotating logs seamlessly.\n\n### Debugging Early-Initialization Errors\n\nIf daemons fail during early initialization before logfiles are set up, run them directly to see stderr output. The `--address` flag is mandatory:\n\n```bash\n# Run user daemon directly\ntelepresence userd --logfile - --address :8083\n\n# Run root daemon directly (requires sudo)\nsudo telepresence rootd --logfile - --address :8084\n```\n\n### Profiling the Daemons\n\nEnable [pprof](https://pkg.go.dev/net/http/pprof) profiling:\n\n```bash\ntelepresence quit -s\ntelepresence connect --userd-profiling-port 6060 --rootd-profiling-port 6061\n# Then browse http://localhost:6060/debug/pprof/\n```\n\n### Dumping Goroutine Stacks\n\nSend SIGQUIT to a daemon to dump goroutine stacks to its log file. On Windows, use profiling instead.\n\n### RBAC Testing\n\nTo test with limited RBAC privileges:\n\n```bash\nkubectl apply -f k8s/client_rbac.yaml\nkubectl get sa telepresence-test-developer -o \"jsonpath={.secrets[0].name}\"\n# Get the token from the secret and configure kubectl\nkubectl get secret <secret-name> -o \"jsonpath={.data.token}\" | base64 --decode\nkubectl config set-credentials telepresence-test-developer --token <token>\nkubectl config use-context telepresence-test-developer\n```\n\n## Releases\n\nTo create a release, set `TELEPRESENCE_VERSION` and run `make prepare-release`. This creates two annotated tags (`vX.Y.Z` and `rpc/vX.Y.Z`) and a commit updating go.mod references. Pushing the tags and branch triggers the release workflow.\n\n**Important:** A tag push publishes the release and cannot be taken back. Never push the tags directly after `make prepare-release`. Push only the branch, open a PR for it, and follow `/ship-release` (`.claude/skills/ship-release`), which drives the release PR's CI (including the required `regression` gate), creates the docs PR in the telepresence.io repository, and pushes the tags only after everything is green. The command blocks below show the mechanics, not the order.\n\n```bash\n# Test release (marked as pre-release, not promoted to latest)\nexport TELEPRESENCE_VERSION=v2.27.0-test.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# Release candidate\nexport TELEPRESENCE_VERSION=v2.27.0-rc.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# GA release (becomes \"latest\", updates Homebrew)\nexport TELEPRESENCE_VERSION=v2.27.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n```\n\nVersion formats:\n- `vX.Y.Z-test.N` - Test release (pre-release)\n- `vX.Y.Z-rc.N` - Release candidate (pre-release)\n- `vX.Y.Z` - GA release (marked as latest, triggers Homebrew update)\n\n### Changelog\n\nWhen adding entries to `CHANGELOG.yml` for an upcoming release:\n- Use `date: (TBD)` for unreleased versions\n- The `make prepare-release` command will set the actual date when `TELEPRESENCE_VERSION` is a GA version (e.g., `v2.27.0`)\n- After modifying `CHANGELOG.yml`, run `make docs-files` to regenerate documentation\n\n### Documentation Website\n\nThe documentation website at [telepresence.io](https://telepresence.io) is managed in the [telepresenceio/telepresence.io](https://github.com/telepresenceio/telepresence.io) repository. When creating a GA release, update the website by running `make generate-version` in that repository with:\n- `DOCS_BRANCH` - Branch in this repository containing the docs (e.g., `release/v2`)\n- `DOCS_VERSION` - Major.minor version to generate or update (e.g., `2.27`)\n\nSee the telepresence.io repository for full instructions.\n\n### macOS Installer Signing and Notarization\n\nThe macOS `.pkg` installers are signed and notarized to pass Gatekeeper verification. The signing process uses a protected GitHub Environment to secure the signing credentials.\n\n#### Environment Setup\n\nThe `build-macos-pkg` job uses the `macos-signing` environment, which must be configured in the repository settings:\n\n1. Go to https://github.com/telepresenceio/telepresence/settings/environments\n2. Create an environment named `macos-signing`\n3. Enable \"Required reviewers\" and add authorized personnel\n4. Optionally restrict deployment branches to `release/*`\n5. Add the following secrets to the environment (not repository-level):\n\n| Secret Name | Description |\n|-------------|-------------|\n| `MACOS_CERTIFICATE_P12` | Base64-encoded P12 file containing both Application and Developer ID Installer certificates |\n| `MACOS_CERTIFICATE_PASSWORD` | Password for the P12 file |\n| `MACOS_SIGN_APPLICATION` | Developer ID Application certificate name (e.g., `Developer ID Application: Your Name (TEAMID)`) |\n| `MACOS_SIGN_INSTALLER` | Developer ID Installer certificate name (e.g., `Developer ID Installer: Your Name (TEAMID)`) |\n| `MACOS_NOTARIZE_APPLE_ID` | Apple ID email for notarization |\n| `MACOS_NOTARIZE_TEAM_ID` | Apple Developer Team ID |\n| `MACOS_NOTARIZE_PASSWORD` | App-specific password for notarization |\n\n#### Release Workflow\n\nWhen a release tag is pushed:\n1. All platform binaries (Linux, Windows, macOS) are built immediately\n2. Linux `.deb`/`.rpm` and Windows `.exe` installers are built\n3. The release is published with all binaries and Linux/Windows installers\n4. The `build-macos-pkg` job waits for approval from a required reviewer\n5. Once approved, signed `.pkg` installers are built and added to the release\n\nThis design ensures:\n- **Emergency releases can proceed** without the signing approver being available (all binaries and Linux/Windows installers are released)\n- **Signing credentials are protected** by requiring explicit approval before they are exposed\n- **Signed packages are added later** when the approver reviews and approves the job\n\nIf the environment is not configured or never approved, the release will contain macOS standalone binaries but not `.pkg` installers.\n\n#### Obtaining the Certificates\n\nYou need an [Apple Developer Program](https://developer.apple.com/programs/) membership ($99/year) to obtain signing certificates.\n\n1. **Create certificates in Apple Developer Portal:**\n   - Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list)\n   - Click the + button to create a new certificate\n   - Create **Developer ID Application** certificate (for signing binaries)\n   - Create **Developer ID Installer** certificate (for signing .pkg files)\n   - Download both certificates and double-click to install in Keychain Access\n\n2. **Find your Team ID:**\n   - Go to [Membership Details](https://developer.apple.com/account#MembershipDetailsCard)\n   - Copy the Team ID (10-character alphanumeric string)\n   - Set as `MACOS_NOTARIZE_TEAM_ID`\n\n3. **Find the certificate names:**\n   - Open Keychain Access and look under \"My Certificates\"\n   - The names will be like:\n     - `Developer ID Application: Your Name (TEAMID)` → `MACOS_SIGN_APPLICATION`\n     - `Developer ID Installer: Your Name (TEAMID)` → `MACOS_SIGN_INSTALLER`\n   - You can also list them with: `security find-identity -v -p codesigning`\n\n4. **Export certificates to P12:**\n   ```bash\n   # Export each certificate from Keychain Access:\n   # - Right-click certificate → Export\n   # - Choose .p12 format\n   # - Set a strong password (will be MACOS_CERTIFICATE_PASSWORD)\n\n   # If you have both in separate .p12 files, you can import them together\n   # or export them together from Keychain Access by selecting both\n\n   # Base64-encode for GitHub secrets:\n   base64 -i certificates.p12 | pbcopy\n   # Paste as MACOS_CERTIFICATE_P12\n   ```\n\n5. **Create app-specific password for notarization:**\n   - Go to [appleid.apple.com](https://appleid.apple.com/) → Sign-In and Security → App-Specific Passwords\n   - Generate a new password with a descriptive name (e.g., \"GitHub Actions Notarization\")\n   - Copy the generated password → `MACOS_NOTARIZE_PASSWORD`\n   - Use your Apple ID email → `MACOS_NOTARIZE_APPLE_ID`\n\n#### Testing Locally\n\nTo test signing locally before configuring GitHub secrets:\n\n```bash\n# Set environment variables\nexport MACOS_SIGN_APPLICATION=\"Developer ID Application: Your Name (TEAMID)\"\nexport MACOS_SIGN_INSTALLER=\"Developer ID Installer: Your Name (TEAMID)\"\nexport MACOS_NOTARIZE_APPLE_ID=\"your@email.com\"\nexport MACOS_NOTARIZE_TEAM_ID=\"ABCD123456\"\nexport MACOS_NOTARIZE_PASSWORD=\"xxxx-xxxx-xxxx-xxxx\"\n\n# Build the signed and notarized package\ncd build-aux/pkg-installer\nVERSION=2.26.0 ./build-pkg.sh\n\n# Verify the signature\npkgutil --check-signature ../../build-output/Telepresence.pkg\nspctl --assess --type install ../../build-output/Telepresence.pkg\n```\n"},"files":{".claude/agents/proto-rpc-reviewer.md":"---\nname: proto-rpc-reviewer\ndescription: Use when reviewing changes to any .proto file under rpc/ or to the Go bindings generated from them. Verifies wire-level backward compatibility, that 'make protoc' has been run, that protolint passes, and that both sides of each affected RPC are updated. Surfaces incompatibilities that would break older clients, older traffic-managers, or older traffic-agents talking to a new peer.\ntools: Read, Grep, Glob, Bash\n---\n\nYou are the gRPC contract reviewer for the telepresence repository.\n\n## Communication boundaries you must consider\n\nThe repo defines four RPC surfaces; a single proto edit can ripple across them:\n\n| Boundary                               | Proto package        |\n|----------------------------------------|----------------------|\n| client/userd ↔ traffic-manager         | `rpc/manager/`       |\n| client ↔ user daemon                   | `rpc/connector/`     |\n| client ↔ root daemon                   | `rpc/daemon/`        |\n| traffic-manager ↔ traffic-agent        | `rpc/agent/`         |\n| auth                                   | `rpc/authenticator/` |\n| teleroute (docker network driver)      | `rpc/teleroute/`     |\n| shared types                           | `rpc/common/`        |\n\nEach daemon ships independently: an older client may talk to a newer traffic-manager, a newer traffic-manager may inject an older traffic-agent (mismatched manifest), and a newer agent may run alongside an older sidecar in another pod. Wire compatibility is therefore mandatory, not optional.\n\n## Checks you must run\n\n1. **Wire compatibility:**\n   - Field numbers must never be reused or repurposed.\n   - Field types must not change (e.g., int32 → int64 silently corrupts).\n   - Enum values must not be renumbered; only appended.\n   - `optional` and `repeated` are part of the wire contract; do not flip.\n   - Removing a field requires `reserved` to lock the number/name.\n2. **Generated code is in sync:** Confirm `make protoc` has been run — check that .pb.go files in the same package are touched in the same change. If not, flag and recommend running it.\n3. **Lint:** Confirm `protolint` would pass against the configured rules in `.protolint.yaml` (line length 120, ENUM_FIELD_NAMES_PREFIX disabled). Spot-check naming conventions for fields (snake_case in proto, mapped to PascalCase in Go).\n4. **Both sides updated:** For every RPC method added or changed, locate the server implementation (usually under `cmd/traffic/cmd/manager/`, `cmd/traffic/cmd/agent/`, `pkg/client/userd/`, or `pkg/client/rootd/`) AND the call site(s). If only one side is touched, flag it.\n5. **Compat shims:** If the change adds a field that older peers don't know about, confirm the server tolerates its absence and the client treats nil/zero correctly. Reject any change that requires a synchronized upgrade of both sides.\n\n## Reporting format\n\nReturn a punch list, not prose. For each finding:\n\n- **Severity:** Blocker / Risk / Nit\n- **Where:** file:line\n- **Why:** one sentence\n- **Fix:** one sentence\n\nEnd with a one-line verdict: \"Safe to merge\", \"Needs follow-up\", or \"Blocked\".\n\n## What NOT to do\n\n- Do not edit any files. You are a reviewer.\n- Do not run `make protoc` yourself; report whether it appears to have been run and let the caller decide.\n- Do not chase code-style nits unrelated to the proto/RPC contract.\n",".claude/skills/changelog-entry/SKILL.md":"---\nname: changelog-entry\ndescription: Add a new entry to CHANGELOG.yml under the current unreleased version (or create the version block if needed), then regenerate documentation. Use when the user says things like \"add a changelog entry\", \"log this fix in the changelog\", or \"/changelog-entry\".\n---\n\n# changelog-entry\n\nAdds an entry to `CHANGELOG.yml` following the schema documented in the file header, then regenerates the derived documentation.\n\n## Inputs to gather (in order)\n\n1. **type** — one of `bugfix`, `feature`, `security`, `change`. If the user describes the change but does not pick a type, infer it:\n   - \"fixes/resolves/closes a bug\" → `bugfix`\n   - \"adds support for / introduces / new\" → `feature`\n   - \"CVE / vulnerability / hardens\" → `security`\n   - anything else affecting behavior → `change`\n2. **title** — short (≤80 chars), sentence-cased, no trailing period.\n3. **body** — 2-3 sentences. **This field is HTML, not markdown.** Use `<code>...</code>` for code, `<a href=\"...\">...</a>` for links. Prefer YAML's `>-` folded scalar so line wrapping doesn't leak literal newlines.\n4. **docs** *(optional)* — path to a docs page under `docs/` if the entry deserves a \"Learn more\" link.\n5. **image** *(optional)* — path under the `release-notes` directory if there's a visual.\n\n## Steps\n\n1. Read the top of `CHANGELOG.yml`. The first `items:` entry is the current/upcoming version.\n2. If it has `date: (TBD)`, append the new entry to its `notes:` array.\n3. If the top item is already dated (a shipped release), insert a NEW `- version: <next>` block above it with `date: (TBD)` and the single new note. Ask the user for the next version number — do not invent it.\n4. Match existing indentation exactly (2 spaces). YAML is whitespace-sensitive.\n5. After saving, run `make docs-files` to regenerate `docs/release-notes.md`, `docs/release-notes.mdx`, and `docs/variables.yml`. (The PostToolUse hook in `.claude/settings.json` will also try to do this; running it explicitly here makes the success/failure visible.)\n6. Show the user the diff: `git diff CHANGELOG.yml docs/release-notes.md docs/release-notes.mdx docs/variables.yml`.\n\n## Schema reference (from CHANGELOG.yml header)\n\n```yaml\nitems:\n  - version: 2.28.0\n    date: (TBD)              # or YYYY-MM-DD\n    notes:\n      - type: bugfix         # bugfix | feature | security | change\n        title: Short title\n        body: >-\n          Two or three sentences describing the change and why it\n          is noteworthy.  This is HTML.\n        docs: optional/path\n        image: optional/path\n```\n\n## Things to avoid\n\n- Do not edit `docs/release-notes.md`, `docs/release-notes.mdx`, or `docs/variables.yml` directly — they are generated.\n- Do not include markdown syntax in `body`; it is rendered as HTML.\n- Do not set `date:` to a concrete date for upcoming versions; `make prepare-release` does that automatically for GA versions.\n",".claude/skills/changelog-entry/agents/openai.yaml":"policy:\n  allow_implicit_invocation: true\n",".claude/skills/prepare-release/SKILL.md":"---\nname: prepare-release\ndescription: Create the local release commit and tags by setting TELEPRESENCE_VERSION and running make prepare-release. Stops at the local commit+tags - pushing is the ship-release skill's job. Use when the user explicitly asks to prepare a release, RC, or test build. User-only.\ndisable-model-invocation: true\n---\n\n# prepare-release\n\nWraps the `make prepare-release` step so the local-tag-creation portion of a release is one explicit user action, not a chain of remembered commands. Stops at local tags; the `ship-release` skill takes over from there.\n\nThis is **user-only** by design (`disable-model-invocation: true`). The tags this skill creates will eventually drive a public release, so creating them must be an explicit user decision — never a side-effect of Claude inferring intent.\n\n## Confirm before doing anything\n\nAsk the user explicitly:\n\n1. **Version string** (`TELEPRESENCE_VERSION`) — must be one of:\n   - `vX.Y.Z-test.N` — pre-release, no Homebrew, no \"latest\"\n   - `vX.Y.Z-rc.N` — pre-release, no Homebrew, no \"latest\"\n   - `vX.Y.Z` — GA, marked latest, triggers Homebrew update\n2. **Branch** — should be a release branch (typically `release/v2`). Refuse to proceed from `main`-style branches.\n3. **Working tree** — must be clean. Run `git status`; if there are uncommitted or untracked files relevant to the build, stop.\n4. **CHANGELOG.yml status** — the top entry should have version matching `TELEPRESENCE_VERSION` (without the leading `v`). If it's still `date: (TBD)`, that's expected: `make prepare-release` sets the date for GA versions.\n\nShow all four checks to the user before running anything. Wait for explicit \"go\".\n\n## Steps\n\n```bash\nexport TELEPRESENCE_VERSION=vX.Y.Z[-suffix.N]\nmake prepare-release\n```\n\nThis creates:\n- An annotated tag `vX.Y.Z[-suffix.N]`\n- An annotated tag `rpc/vX.Y.Z[-suffix.N]`\n- A commit that bumps go.mod references inside the repo\n\nVerify with:\n\n```bash\ngit log -1 --stat\ngit tag --points-at HEAD\n```\n\n## Next: hand off to `ship-release`\n\nThis skill stops here, with the local commit and the two annotated tags. **Do not push anything.** To carry the release through CI, the docs PR, the Releases workflow, and the PR merges, invoke the `ship-release` skill (after pushing the branch and opening a PR on it — that's a manual handoff step the user does between the two skills).\n\n## Refuse to\n\n- Push anything (branch, commit, or tags). That's `ship-release`'s job.\n- Skip `make prepare-release` and just tag manually. The make target updates go.mod references; manual tagging skips that and ships a broken module.\n- Re-run `make prepare-release` on top of a previous attempt without first cleaning up the leftover tags. If `git tag --points-at HEAD` already lists the target tag, stop and report — the user has to decide whether to delete it.\n",".claude/skills/prepare-release/agents/openai.yaml":"policy:\n  allow_implicit_invocation: false\n",".claude/skills/regression-tests/SKILL.md":"---\nname: regression-tests\ndescription: Run, scope, or debug telepresence regression tests under regression_test/ — the integration-level suite. Use when the user wants to run an area, suite, or single test, debug a failure, or says \"/regression-tests\". Runs `go test ./regression_test` scoped with -run, in the background, writing to a log file so heavy output stays out of context.\n---\n\n# regression-tests\n\nRuns the telepresence regression suite from the main conversation, where this\nharness's shell-env quirks are known.\n\n## Background to assume\n\n- Tests live under `regression_test/` and need a working k8s cluster (kind /\n  minikube / Docker Desktop) plus images it can reach. For a local cluster set\n  `RTEST_REGISTRY=local` and LOAD the images into it rather than pushing.\n- `regression_test/README.md` is the reference: fixture-engine rules, the\n  RTEST_* table, catalogs, labels, coverage. Read it before debugging a\n  fixture problem.\n- **The shell environment always wins; there is no config file.** That means a\n  stale `export` in the user's shell silently changes a run.\n\n## Scoping: plain `go test -run`\n\nAreas are ordinary Go tests, suites and methods are subtests, so one `-run`\nexpression selects at any depth:\n\n```\ngo test ./regression_test -run '^TestIntercept$'\ngo test ./regression_test -run '^TestIntercept$/^HeaderFilter$'\ngo test ./regression_test -run '^TestIntercept$/^HeaderFilter$/^Test_PathPrefix$'\n```\n\nThere is no `TEST_SUITE`/`TEST_NAME` indirection and no make-argument dance:\ninline `VAR=value` prefixes work in this harness, so run `go test` directly\nand keep `make check-regression` for the full unscoped suite.\n\nAlways pass `-count=1` (results must never come from the test cache) and a\n`-timeout` that fits the scope: minutes for one suite, `-timeout=100m` for a\nfull run.\n\n## The run command\n\n```\nTELEPRESENCE_REGISTRY=local TELEPRESENCE_VERSION=<version> \\\n  RTEST_CONTEXT=<context> RTEST_TEARDOWN=1 \\\n  go test -count=1 -timeout=30m -run '^TestArea$/^Suite$' ./regression_test \\\n  > /tmp/rtest-suite.log 2>&1\n```\n\n- `RTEST_CONTEXT` pins the kube context. Pin it explicitly whenever the\n  machine has more than one cluster — the default is the kubeconfig's current\n  context, which is one `kubectx` away from being the wrong cluster.\n- `RTEST_TEARDOWN=1` destroys the run's resources at the end. Without it, dev\n  mode keeps namespaces, the release, and the connection for the next run to\n  adopt, which is what makes a scoped rerun take seconds.\n- `RTEST_FRESH=1` ignores adoptable resources (use when a previous run left\n  something suspect). CI implies fresh + teardown.\n- `RTEST_LABELS` / `RTEST_SKIP_LABELS` select on `compat-core`, `slow`,\n  `stress`, `flaky-retry`.\n\n## CRITICAL: a stale TELEPRESENCE_VERSION silently poisons the build\n\nThe version under test is read from the binary itself, and `make build` stamps\nit from `TELEPRESENCE_VERSION`. If the user's shell exports an old value, the\nbinary gets that version, the manager image tag no longer matches, and the run\nfails at image pull with a version that appears nowhere in your command.\n\nAlways pass `TELEPRESENCE_VERSION` explicitly to BOTH `make build` and the\n`go test` invocation, with the same value.\n\n## Rebuild before running\n\nThe suite runs the prebuilt binary plus the cluster-side images, so rebuild\nwhatever changed:\n\n- client-side Go (`pkg/`, `cmd/telepresence`) **and any `charts/` change**:\n  `make build` — the chart is go:embedded in the client binary, so a\n  chart-only edit without a rebuild silently installs the OLD chart.\n- manager / agent (`cmd/traffic`, `charts/`): `make load-images` (or\n  `make load-tel2-image`), so the cluster gets the new image.\n- `--docker` tests: `make client-image` — the daemon container runs on the\n  workstation, so it only needs to exist locally.\n\n`RTEST_REGISTRY=local` makes the manager use `pullPolicy=Never`. Reserve\n`make push-images` and a real registry for a remote cluster.\n\n## Keep heavy output out of context\n\nThis runs in the main thread, so do NOT Read or `tail` the whole log.\n\n1. Launch with `run_in_background: true`, redirecting to a fresh path.\n2. On completion, read only the summary:\n   `grep -E 'passed,|^ok|^--- FAIL|^FAIL' /tmp/rtest-suite.log | tail -5`\n   The runner's own last line is\n   `[rtest] run <stamp>: N passed, N failed, N skipped (N fixture actions)`.\n3. For a failure, the per-test artifacts are under\n   `build-output/rtest/logs/<stamp>/<TestPath>/` (cli.log, daemon logs), and\n   `manifest.json` records every test's outcome and labels.\n\n## Leftover state\n\n- `make rtest-clean` removes everything the framework created, in the cluster\n  and locally.\n- A daemon from a killed run: `telepresence quit -s`.\n- One rtest run per cluster at a time — resource names are stable by design,\n  which is what makes adoption work.\n- A leftover manager from unrelated work (\"traffic-manager in namespace X\n  already manages namespace Y\") blocks an unrestricted install: find it with\n  `kubectl get secret -A -l owner=helm`, and remember the chart also leaves\n  cluster-scoped resources (webhook config, ClusterRole/Binding) that\n  namespace deletion does not remove.\n\n## Workflow\n\n1. **Identify** the area/suite: areas are directories under\n   `regression_test/suites/`, and each suite is an `rt.Suite` type registered\n   in its `init()`.\n2. **Decide the rebuild** (see above) and run it with an explicit\n   `TELEPRESENCE_VERSION`.\n3. **Run scoped**, in the background, to a fresh log path.\n4. **Summarize:** the command, pass/fail/skip counts, failing test names, the\n   smallest excerpt explaining each failure, and the next concrete action.\n\n## Don't\n\n- Don't run the full suite unscoped without explicit user instruction — it is\n  roughly an hour serial; `make check-regression SHARD=1|2|3` runs a third of\n  it (the shard/area mapping lives in build-aux/main.mk).\n- Don't run `go test -list` or a deliberately non-matching `-run` to \"check\n  what exists\": the harness provisions real cluster resources before selection,\n  so it costs a full setup cycle. Grep the suite files instead.\n- Don't `make clobber` or destroy local images without asking.\n- Don't edit generated files: `docs/reference/cli/**`, `DEPENDENCIES.md`,\n  `DEPENDENCY_LICENSES.md`, `docs/release-notes*`.\n",".claude/skills/regression-tests/agents/openai.yaml":"policy:\n  allow_implicit_invocation: true\n",".claude/skills/ship-release/SKILL.md":"---\nname: ship-release\ndescription: Drive a Telepresence release from a prepared branch all the way through CI, docs, the Releases workflow, and PR merges. Assumes `make prepare-release` has already been run locally and the branch with that commit was pushed and a PR opened. Use when the user says \"ship the release\", or \"complete the release\". User-only.\ndisable-model-invocation: true\n---\n\n# ship-release\n\nEnd-to-end driver for releasing Telepresence. Picks up where `prepare-release` left off and carries the change through:\n\n1. Telepresence release PR (CI green + `regression` green)\n2. Docs PR in `../telepresence.io`\n3. Tag push, Releases workflow, merge of both PRs.\n\nThis is **user-only** (`disable-model-invocation: true`). A release is publicly visible and partially irreversible (tags push to GitHub, Homebrew updates for GA). Claude must never invoke this on its own.\n\n## Preconditions to verify before doing anything\n\nRun each check and stop with a clear message if any fails:\n\n1. **CWD is the telepresence repo.** `git rev-parse --show-toplevel` ends in `telepresence`.\n2. **`make prepare-release` has been run.** The current HEAD must carry both `vX.Y.Z` and `rpc/vX.Y.Z` annotated tags locally:\n   ```\n   git tag --points-at HEAD | sort\n   ```\n   Two entries expected. If the tag list is empty or missing the `rpc/` peer, stop — the user needs to run `make prepare-release` first.\n3. **Branch is pushed.** Capture `tp_branch=$(git branch --show-current)`, then:\n   ```\n   git ls-remote --exit-code origin \"refs/heads/$tp_branch\"\n   ```\n   If this fails, stop and tell the user to push the branch first.\n4. **PR exists.** `gh pr view \"$tp_branch\" --json number,state,url,headRefName`. If no PR, stop.\n5. **Sibling docs repo present.** `test -d ../telepresence.io && test -d ../telepresence.io/.git`. If not, stop.\n\nCapture once and reuse throughout:\n\n- `tp_branch` — name of the prepare-release branch.\n- `tp_version` — pick the non-`rpc/` tag from `git tag --points-at HEAD` (e.g. `v2.28.0`).\n- `docs_version` — `echo \"$tp_version\" | sed -E 's/^v([0-9]+\\.[0-9]+).*/\\1/'` (e.g. `2.28`).\n- `pr_number` — from `gh pr view`.\n\n## Phase 1 — Drive the Telepresence release PR\n\n### 1.1 Verify branch and PR (already done in preconditions)\n\n### 1.2 Wait for all checks except `regression` to be green\n\nUse:\n\n```\ngh pr checks \"$tp_branch\" --json name,state,conclusion\n```\n\nFilter out the rows whose name is exactly `regression` or `node_agent_docker_runtime` (neither has been triggered yet — the label triggers them). Match the name exactly: `regression_compat` is a different job, and it only runs behind the `compatibility test` label. For every remaining row:\n\n- `state == \"COMPLETED\"` and `conclusion == \"SUCCESS\"` → green\n- `conclusion ∈ {\"FAILURE\",\"CANCELLED\",\"TIMED_OUT\",\"ACTION_REQUIRED\"}` → **stop**. Report the failing check name and a short excerpt from `gh run view <run-id> --log-failed`. Do not advance.\n- Anything else (`IN_PROGRESS`, `QUEUED`, `PENDING`) → keep waiting.\n\n**Polling cadence:** these checks (lint, unit tests, license, image-scan) typically finish in 5-15 min. Use `ScheduleWakeup` with `delaySeconds=180` while any check is still running. Do not tight-loop with sleeps.\n\n### 1.3 Wait for `regression` to be green\n\n`regression` starts automatically with the push (the release branch lives in\nthis repository); there is nothing to trigger. If it needs another attempt,\nuse \"Re-run all jobs\" on its workflow run (`gh run rerun <run-id>`).\n\nThe regression suite runs as three parallel shards (~30 min including cluster setup), summed into the single `regression` context; the node-agent job runs beside them. Use `ScheduleWakeup` with `delaySeconds` around **900**. Poll with the same `gh pr checks` query, looking at the `regression` and `node_agent_docker_runtime` rows.\n\n- Success → continue to Phase 2.\n- Failure / cancellation → **stop and report**. Pull failed-step logs with `gh run view <run-id> --log-failed`.\n- Still running after ~90 minutes → tell the user and stop (workflow may be stuck).\n\n## Phase 2 — Create the docs PR\n\nDone in the sibling repo `../telepresence.io`. Each shell step below is a separate Bash call (no `&&` chains), and `cd` to switch repos.\n\n### 2.1 Pull master\n\n```\ncd ../telepresence.io\ngit checkout master\ngit pull\n```\n\n### 2.2 Create branch with the same name as the telepresence PR branch\n\n```\ngit checkout -b \"$tp_branch\"\n```\n\nIf that branch already exists locally from a previous attempt, stop and ask whether to reuse, reset, or rename.\n\n### 2.3 + 2.4 Export variables\n\n```\nexport DOCS_VERSION=\"$docs_version\"   # e.g. 2.28 — note: no patch number\nexport DOCS_BRANCH=\"$tp_branch\"\n```\n\n(Per CLAUDE.md, `export` in its own Bash call, then use in subsequent calls. Shell state persists between calls in a session.)\n\n### 2.5 Generate\n\n```\nmake generate-version\n```\n\n### 2.6 Verify output\n\n```\nls versioned_docs/version-\"$DOCS_VERSION\"\ngit status\n```\n\nExpectations:\n\n- **Minor release** (first time this `2.X` is generated): the directory `versioned_docs/version-$DOCS_VERSION/` appears as untracked.\n- **Bugfix release** (directory already existed): files within it are modified.\n\nIf `versioned_docs/version-$DOCS_VERSION` is absent or `git status` shows no changes, **stop and report** — `make generate-version` did not do anything useful.\n\n### 2.7 Build the site locally before pushing\n\nNetlify (the `deploy/netlify`, `Pages changed`, `Header rules`, `Redirect rules`\nchecks) and the `Check`/`Lint` GitHub jobs all run `yarn build` (docusaurus\nbuild). Run it locally first so a broken build is caught here, not after a\npush-and-wait CI cycle:\n\n```\n# If node_modules is absent: yarn install --frozen-lockfile\nyarn build\n```\n\n- Exit 0 → the production build (including the new `version-$DOCS_VERSION`)\n  compiled. Proceed to the PR.\n- Non-zero → **stop and do not push.** Read the error; it names the offending\n  file and line.\n\n**Most common failure: MDX parse error in `release-notes.mdx`.** A `.mdx` file\nis JSX, so literal `{` / `}` **inside an HTML element** like\n`<code>{cmd, stdout}</code>` are parsed as JS expressions and fail with\n`Could not parse expression with acorn`. (Braces inside Markdown backtick\nspans — `` `{tcp|udp}` `` — are safe.) These release-notes files are generated,\nso fix the **source**, not the generated copy:\n\n1. In the telepresence repo, edit the offending `CHANGELOG.yml` entry to remove\n   the literal braces (rephrase, e.g. `<code>cmd</code>/<code>stdout</code>`, or\n   move the snippet into a backtick span), then `make docs-files`.\n2. Commit + push that fix to the release branch (it belongs in the release PR).\n3. Back in the docs repo, re-run `make generate-version` to re-pull the fixed\n   docs, then `yarn build` again before continuing.\n\n### 2.8 Create the PR\n\n```\ngit add versioned_docs/version-\"$DOCS_VERSION\" versioned_sidebars docusaurus.config.js versions.json\n# (Add only the files that actually changed — git status will tell you which of the\n#  above moved; for a fresh minor you'll likely see all of them, for a bugfix only some.)\ngit commit -s -S -m \"Generate docs for telepresence $tp_version\"\ngit push -u origin \"$tp_branch\"\ngh pr create --base master --head \"$tp_branch\" \\\n  --title \"Generate docs for telepresence $tp_version\" \\\n  --body \"Generated with \\`make generate-version\\` DOCS_VERSION=$docs_version DOCS_BRANCH=$tp_branch.\"\n```\n\nCapture `docs_pr_number` from the `gh pr create` output URL.\n\nDo not include \"Co-Authored-By\" or \"Generated with\" trailers in the commit message or the PR body (per global preferences).\n\n### 2.9 Monitor the docs PR checks\n\n```\ngh pr checks \"$tp_branch\" --json name,state,conclusion\n```\n\nSame polling rules as Phase 1.2. If anything fails, **stop and report**.\n\n## Phase 3 — Release\n\n`cd` back to the telepresence repo for step 3.1 and 3.3a.\n\n### 3.1 Push the release tags\n\n```\ngit push origin \"$tp_version\" \"rpc/$tp_version\"\n```\n\nThis triggers the **Releases** workflow (`.github/workflows/release.yaml`). The release PR is still unmerged at this point — that is intentional. Merging now would create a new commit and move the branch tip away from the tagged commit.\n\n### 3.2 Monitor the Releases workflow\n\n```\ngh run list --workflow=release.yaml --limit 1 --json databaseId,status,conclusion,url\ngh run view <id> --json jobs\n```\n\nThe workflow requires manual approval of a protected GitHub environment (`macos-signing`) containing secrets for macOS signing. Wait for this up to **24 hours**. Poll with `ScheduleWakeup` at `delaySeconds=1800` (or longer when overnight). Surface the workflow URL early so the user can chase the approver.\n\n- If the workflow completes successfully → continue to 3.3.\n- If a job other than `build-macos-pkg` fails → **stop and report**.\n- If `build-macos-pkg` itself is never approved within 24h → tell the user; per CLAUDE.md the release still ships without `.pkg` installers, and the user can decide whether to proceed to 3.3 anyway.\n\n### 3.3 Merge both PRs — **GA versions only**\n\n**For pre-release versions (`-test.N`, `-rc.N`): skip this step entirely and\nstop here.** Both PRs stay open until the GA release ships: the release\nbranch accumulates the rc and GA prepare-release commits and merges once,\nafter GA, and the docs PR must not publish the new version's docs on\ntelepresence.io before GA exists (regenerate it from the GA branch before\nmerging). The rc's GitHub pre-release and its tags are the only public\nartifacts of a pre-release ship.\n\nFor a GA version: order does not matter. Both must use a **merge commit**\n(CLAUDE.md: never squash, never rebase).\n\n```\n# telepresence PR (in telepresence repo)\ngh pr merge \"$tp_branch\" --merge\n\n# docs PR (in ../telepresence.io)\ncd ../telepresence.io\ngh pr merge \"$tp_branch\" --merge\n```\n\nVerify each merged: `gh pr view \"$tp_branch\" --json state` should report `MERGED`.\n\n## Long-wait strategy\n\n- Anything under 5 min → don't sleep; just poll once.\n- 5-30 min waits (Phase 1.2 non-regression checks) → `ScheduleWakeup` with `delaySeconds=180`.\n- 30-60 min waits (Phase 1.3 `regression`) → `ScheduleWakeup` with `delaySeconds=1200`.\n- Hours-to-overnight (Phase 3.2 macOS signing approval) → `ScheduleWakeup` with `delaySeconds=1800` or longer.\n\nEach wake-up: re-fetch state, decide green/red/still-waiting, schedule the next wake or advance.\n\n## What \"stop and report\" means\n\n- Do not advance to the next numbered step.\n- Surface: the step that failed, the check/run name(s), the run URL(s), and a short excerpt from `gh run view <id> --log-failed`.\n- Do not retry automatically. Wait for the user to direct.\n- Do not delete branches, force-push, or close PRs. The user decides what to do.\n\n## What this skill must NEVER do\n\n- Run `make prepare-release` itself — that's a separate skill and a separate decision.\n- Push tags before all required PR checks are green (Phase 1 must complete first).\n- Merge the release PR or the docs PR for a pre-release (`-test.*`/`-rc.*`) version — both stay open until GA (see 3.3).\n- Merge PRs as squash or rebase — both repos require merge commits.\n- Trigger `regression` by any means other than the push itself or a re-run of its workflow run.\n- Approve the `macos-signing` environment programmatically — that requires a human reviewer.\n- Force-push or delete the release branch.\n",".claude/skills/ship-release/agents/openai.yaml":"policy:\n  allow_implicit_invocation: false\n","AGENTS.md":"# AGENTS.md\n\nThis file provides guidance for contributors and AI assistants working with this repository.\n\n## Project Overview\n\nTelepresence is a Kubernetes development tool that enables fast local development by connecting your local workstation to a Kubernetes cluster. It allows developers to run services locally while accessing cluster resources and intercepting traffic from the cluster to their local machine.\n\n## Git Workflow\n\n- Never commit directly to the `release/v2` branch. Always create a feature branch with a name following the pattern `username/topic` (e.g., `thallgren/fix-dns-resolution`).\n- All commits must be signed and signed-off (`git commit -s -S`).\n- Limit commit message subjects to 72 characters. Do not wrap subjects;\n  rewrite them shorter instead. Wrap commit message body lines at 72\n  characters by default unless preserving exact external text requires a\n  longer line.\n- **Always run `make lint` and fix every reported issue before pushing.** This is non-negotiable — CI runs the same linters and a push with lint errors wastes a CI cycle. If `make lint` finds problems, fix them in the appropriate commit (use `git commit --fixup=<sha>` followed by `GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash --gpg-sign <base>` to fold them in) before pushing.\n- Push the branch and create a pull request for review.\n- Always merge PRs with a merge commit (never squash or rebase).\n\n## Design Plans\n\nMajor work (multi-file changes, new features, refactors) starts with a written plan\nunder `docs/plans/<topic>/`, presented for review before implementation begins.\n\nA plan is scaffolding for review, not a lasting artifact. It is removed in the last\ncommit on the PR that implements it. By then, everything in the plan must have been\nimplemented and documented, so the plan no longer has a purpose.\n\n## Build Artifacts\n\nThe Open Source version of Telepresence consists of three artifacts:\n\n**Client-side (runs on developer workstation):**\n- **`telepresence` binary** - The same binary serves as CLI, user daemon, and root daemon.\n- **`telepresence` Docker image** - Used as both user and root daemon when running `telepresence connect --docker`.\n\n**Cluster-side (runs in Kubernetes):**\n- **`tel2` Docker image** - Used by the traffic-manager deployment and injected as traffic-agent sidecars.\n\n## Build Commands\n\n```bash\n# Set required environment variables\nexport TELEPRESENCE_VERSION=v2.x.x-alpha.0  # or use auto-generated version\nexport TELEPRESENCE_REGISTRY=local          # 'local' for Docker Desktop, or 'ghcr.io/telepresenceio'\n\n# Build the telepresence binary\nmake build\n\n# Build Docker images (for local Kubernetes development)\nmake client-image    # Client container image\nmake tel2-image      # Traffic-manager/traffic-agent image\n\n# Build everything for local development\nmake build client-image tel2-image\n\n# Install to system\nmake install\n\n# Clean build artifacts\nmake clean\nmake clobber  # Also removes tools\n```\n\nEnvironment variables:\n- `TELEPRESENCE_REGISTRY` (required) - Docker registry for images. Use `local` for docker-based Kubernetes, or `ghcr.io/telepresenceio` for the release registry.\n- `TELEPRESENCE_VERSION` (optional) - Version string to compile into binaries and images. If not set, auto-generated from CHANGELOG.yml and source hash.\n\nRun `make help` for more information.\n\n### Building on Windows\n\nWindows builds use `build-aux\\winmake.bat` instead of `make` directly. Pass the same parameters as you would to make. The script runs make inside a Docker container with appropriate parameters for Windows binaries.\n\n## Testing\n\n```bash\n# Unit tests\nmake check-unit\n\n# Regression tests (requires a Kubernetes cluster; see the guide below)\nmake check-regression\n\n# One regression area / suite / test — plain go test selection:\ngo test ./regression_test -run 'TestIntercept/HeaderFilter/Test_PathPrefix'\n\n# Chart-value combinations, clusterless:\ngo test ./regression_test/golden\n```\n\nThe regression suite in `regression_test/` is the integration-test\npackage: declarative memoized fixtures, warm-cluster adoption for fast\nscoped runs, coverage instrumentation, and a bidirectional\ncompatibility subset. **Read `regression_test/README.md` before writing or\ndebugging these tests** — it documents the fixture engine's rules (lazy\naccessors, Mutate discipline, spec declarations), the RTEST_* environment,\nthe manager/workload catalogs, labels and platform constraints, coverage,\nand compat runs.\n\n## Linting\n\n```bash\n# Run all linters\nmake lint\n\n# Run Go linter only\nmake lint-go\n\n# Run protobuf linter only\nmake lint-rpc\n\n# Run documentation linter only (link/nav consistency via tools/src/docslint,\n# terminology and stale references via Vale in Docker; config in .vale.ini)\nmake lint-docs\n\n# Auto-fix lint issues\nmake format\n```\n\nLinting uses golangci-lint v2 running in Docker. Configuration is in `.golangci.yml`.\n\n## Code Comments\n\nComments must describe the code as it is. Never write comments that describe a\ntransition — why code was moved, what it replaced, or how it differs from an\nearlier version. The reader sees only the current code, so such comments carry\nno information for them. Keep comments short; avoid long explanations.\n\nOn internal (unexported) functions and methods, keep doc comments minimal: a\nfew lines stating only what the code cannot show, such as a locking-order or\npublication-order invariant. With well-named code, the details live in the\ncode itself; a reader who wants them will read it. Multi-paragraph comments\nthat justify design decisions belong in review discussions, not in the\nsource.\n\n## Code Generation\n\n```bash\n# Regenerate protobuf and license files\nmake generate\n\n# Regenerate protobuf files only\nmake protoc\n\n# Regenerate documentation files (after changing CHANGELOG.yml)\nmake docs-files\n```\n\n**Important:** After modifying `CHANGELOG.yml`, always run `make docs-files` to regenerate documentation files (`docs/release-notes.md`, `docs/release-notes.mdx`, `docs/variables.yml`).\n\n**Important:** All files under `docs/reference/cli/` are generated from Go source code. Do not edit them directly; instead, modify the corresponding Go source and regenerate.\n\n### Updating License Documentation\n\nRun `make generate` and commit changes to `DEPENDENCY_LICENSES.md` and `DEPENDENCIES.md`.\n\n## Documentation\n\nThe documentation under `docs/` aims to follow the\n[Diátaxis](https://diataxis.fr/) framework. Its four quadrants map to the\nlayout like this:\n\n| Diátaxis quadrant | Orientation | Location |\n|-------------------|-------------|----------|\n| Tutorials | learning | `docs/quick-start.md` |\n| How-to guides | task | `docs/howtos/` |\n| Reference | information | `docs/reference/` |\n| Explanation | understanding | `docs/concepts/` |\n\nWhen documenting a new feature, decide which quadrants it needs — typically a\nhow-to guide (how to enable/use it) plus a reference page (its complete\nbehavior, configuration, and limitations) — and keep the quadrants separate:\na how-to gets a task done and links to the reference for details; a reference\ndescribes exhaustively and doesn't teach. Add new pages to the navigation in\n`docs/doc-links.yml`, and run `make lint-docs` (link/nav consistency and\nterminology) before pushing.\n\n## Architecture\n\n### Main Components\n\n1. **CLI/Client** (`cmd/telepresence/`, `pkg/client/cli/`)\n   - Single binary serving as CLI, user daemon, and root daemon\n   - Commands are in `pkg/client/cli/cmd/`\n\n2. **User Daemon (userd)** (`pkg/client/userd/`)\n   - Runs as the user, manages connection to traffic-manager\n   - Handles intercepts, port forwards, cluster communication\n\n3. **Root Daemon (rootd)** (`pkg/client/rootd/`)\n   - Runs with elevated privileges\n   - Manages virtual network interface (VIF) and DNS\n\n4. **Traffic Manager** (`cmd/traffic/cmd/manager/`)\n   - Runs in the Kubernetes cluster (ambassador namespace by default)\n   - Coordinates intercepts between clients and traffic-agents\n\n5. **Traffic Agent** (`cmd/traffic/cmd/agent/`)\n   - Injected as sidecar into intercepted pods\n   - Routes traffic between the pod and the local machine\n\n6. **Agent Init** (`cmd/traffic/cmd/agentinit/`)\n   - Init container for setting up iptables rules in pods\n\n7. **Docker Network Driver** (`cmd/teleroute/`)\n   - Only used when connecting with `--docker` flag\n   - Provides the Docker network that enables communication between the Telepresence daemon container and other containers\n\n### Key Packages\n\n- `pkg/vif/` - Virtual network interface implementation\n- `pkg/tunnel/` - gRPC-based tunneling for network traffic\n- `pkg/dnsproxy/` - DNS resolution and proxying\n- `pkg/agentconfig/` - Traffic-agent configuration\n- `pkg/client/k8s/` - Kubernetes client interactions\n- `pkg/routing/` - Network routing logic\n- `pkg/client/cli/cmd/` - CLI commands. One per file.\n\n### RPC Definitions\n\nProtocol buffers are in `rpc/` with separate packages:\n- `rpc/connector/` - Client-to-userd communication\n- `rpc/daemon/` - Client-to-rootd communication\n- `rpc/manager/` - Client/userd-to-traffic-manager communication\n- `rpc/agent/` - Traffic-manager-to-traffic-agent communication\n\n### Version Parity Between CLI and Daemons\n\nThe CLI never talks to a user or root daemon of a different version.\n`pkg/client/cli/connect/version_check.go` enforces this on every command\nthat reaches a daemon: the host user daemon and root daemon must match the\nclient version exactly, and a containerized user daemon must match on\nmajor.minor.patch. This means changes to `rpc/connector/` and `rpc/daemon/`\nnever need backward-compatibility fallbacks — a new RPC can be assumed to\nexist on the daemon side. Backward compatibility DOES matter for\n`rpc/manager/` and `rpc/agent/`, where the cluster side is upgraded\nindependently of the client.\n\n### Helm Chart\n\nThe traffic-manager Helm chart is in `charts/telepresence-oss/`.\n\n## Debugging and Troubleshooting\n\n### Log Files\n\nThere are three log files:\n- `connector.log` - Output from user daemon: traffic-manager interaction, intercepts, port forwards\n- `daemon.log` - Output from root daemon: networking changes on your workstation\n- `cli.log` - Output from the command line interface\n\nLocations:\n- macOS: `~/Library/Logs/telepresence/`\n- Linux: `~/.cache/telepresence/logs/`\n- Windows: `%USERPROFILE%\\AppData\\Local\\logs`\n\nLogs rotate daily. Use `tail -F <filename>` to watch rotating logs seamlessly.\n\n### Debugging Early-Initialization Errors\n\nIf daemons fail during early initialization before logfiles are set up, run them directly to see stderr output. The `--address` flag is mandatory:\n\n```bash\n# Run user daemon directly\ntelepresence userd --logfile - --address :8083\n\n# Run root daemon directly (requires sudo)\nsudo telepresence rootd --logfile - --address :8084\n```\n\n### Profiling the Daemons\n\nEnable [pprof](https://pkg.go.dev/net/http/pprof) profiling:\n\n```bash\ntelepresence quit -s\ntelepresence connect --userd-profiling-port 6060 --rootd-profiling-port 6061\n# Then browse http://localhost:6060/debug/pprof/\n```\n\n### Dumping Goroutine Stacks\n\nSend SIGQUIT to a daemon to dump goroutine stacks to its log file. On Windows, use profiling instead.\n\n### RBAC Testing\n\nTo test with limited RBAC privileges:\n\n```bash\nkubectl apply -f k8s/client_rbac.yaml\nkubectl get sa telepresence-test-developer -o \"jsonpath={.secrets[0].name}\"\n# Get the token from the secret and configure kubectl\nkubectl get secret <secret-name> -o \"jsonpath={.data.token}\" | base64 --decode\nkubectl config set-credentials telepresence-test-developer --token <token>\nkubectl config use-context telepresence-test-developer\n```\n\n## Releases\n\nTo create a release, set `TELEPRESENCE_VERSION` and run `make prepare-release`. This creates two annotated tags (`vX.Y.Z` and `rpc/vX.Y.Z`) and a commit updating go.mod references. Pushing the tags and branch triggers the release workflow.\n\n**Important:** A tag push publishes the release and cannot be taken back. Never push the tags directly after `make prepare-release`. Push only the branch, open a PR for it, and follow `/ship-release` (`.claude/skills/ship-release`), which drives the release PR's CI (including the required `regression` gate), creates the docs PR in the telepresence.io repository, and pushes the tags only after everything is green. The command blocks below show the mechanics, not the order.\n\n```bash\n# Test release (marked as pre-release, not promoted to latest)\nexport TELEPRESENCE_VERSION=v2.27.0-test.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# Release candidate\nexport TELEPRESENCE_VERSION=v2.27.0-rc.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# GA release (becomes \"latest\", updates Homebrew)\nexport TELEPRESENCE_VERSION=v2.27.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n```\n\nVersion formats:\n- `vX.Y.Z-test.N` - Test release (pre-release)\n- `vX.Y.Z-rc.N` - Release candidate (pre-release)\n- `vX.Y.Z` - GA release (marked as latest, triggers Homebrew update)\n\n### Changelog\n\nWhen adding entries to `CHANGELOG.yml` for an upcoming release:\n- Use `date: (TBD)` for unreleased versions\n- The `make prepare-release` command will set the actual date when `TELEPRESENCE_VERSION` is a GA version (e.g., `v2.27.0`)\n- After modifying `CHANGELOG.yml`, run `make docs-files` to regenerate documentation\n\n### Documentation Website\n\nThe documentation website at [telepresence.io](https://telepresence.io) is managed in the [telepresenceio/telepresence.io](https://github.com/telepresenceio/telepresence.io) repository. When creating a GA release, update the website by running `make generate-version` in that repository with:\n- `DOCS_BRANCH` - Branch in this repository containing the docs (e.g., `release/v2`)\n- `DOCS_VERSION` - Major.minor version to generate or update (e.g., `2.27`)\n\nSee the telepresence.io repository for full instructions.\n\n### macOS Installer Signing and Notarization\n\nThe macOS `.pkg` installers are signed and notarized to pass Gatekeeper verification. The signing process uses a protected GitHub Environment to secure the signing credentials.\n\n#### Environment Setup\n\nThe `build-macos-pkg` job uses the `macos-signing` environment, which must be configured in the repository settings:\n\n1. Go to https://github.com/telepresenceio/telepresence/settings/environments\n2. Create an environment named `macos-signing`\n3. Enable \"Required reviewers\" and add authorized personnel\n4. Optionally restrict deployment branches to `release/*`\n5. Add the following secrets to the environment (not repository-level):\n\n| Secret Name | Description |\n|-------------|-------------|\n| `MACOS_CERTIFICATE_P12` | Base64-encoded P12 file containing both Application and Developer ID Installer certificates |\n| `MACOS_CERTIFICATE_PASSWORD` | Password for the P12 file |\n| `MACOS_SIGN_APPLICATION` | Developer ID Application certificate name (e.g., `Developer ID Application: Your Name (TEAMID)`) |\n| `MACOS_SIGN_INSTALLER` | Developer ID Installer certificate name (e.g., `Developer ID Installer: Your Name (TEAMID)`) |\n| `MACOS_NOTARIZE_APPLE_ID` | Apple ID email for notarization |\n| `MACOS_NOTARIZE_TEAM_ID` | Apple Developer Team ID |\n| `MACOS_NOTARIZE_PASSWORD` | App-specific password for notarization |\n\n#### Release Workflow\n\nWhen a release tag is pushed:\n1. All platform binaries (Linux, Windows, macOS) are built immediately\n2. Linux `.deb`/`.rpm` and Windows `.exe` installers are built\n3. The release is published with all binaries and Linux/Windows installers\n4. The `build-macos-pkg` job waits for approval from a required reviewer\n5. Once approved, signed `.pkg` installers are built and added to the release\n\nThis design ensures:\n- **Emergency releases can proceed** without the signing approver being available (all binaries and Linux/Windows installers are released)\n- **Signing credentials are protected** by requiring explicit approval before they are exposed\n- **Signed packages are added later** when the approver reviews and approves the job\n\nIf the environment is not configured or never approved, the release will contain macOS standalone binaries but not `.pkg` installers.\n\n#### Obtaining the Certificates\n\nYou need an [Apple Developer Program](https://developer.apple.com/programs/) membership ($99/year) to obtain signing certificates.\n\n1. **Create certificates in Apple Developer Portal:**\n   - Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list)\n   - Click the + button to create a new certificate\n   - Create **Developer ID Application** certificate (for signing binaries)\n   - Create **Developer ID Installer** certificate (for signing .pkg files)\n   - Download both certificates and double-click to install in Keychain Access\n\n2. **Find your Team ID:**\n   - Go to [Membership Details](https://developer.apple.com/account#MembershipDetailsCard)\n   - Copy the Team ID (10-character alphanumeric string)\n   - Set as `MACOS_NOTARIZE_TEAM_ID`\n\n3. **Find the certificate names:**\n   - Open Keychain Access and look under \"My Certificates\"\n   - The names will be like:\n     - `Developer ID Application: Your Name (TEAMID)` → `MACOS_SIGN_APPLICATION`\n     - `Developer ID Installer: Your Name (TEAMID)` → `MACOS_SIGN_INSTALLER`\n   - You can also list them with: `security find-identity -v -p codesigning`\n\n4. **Export certificates to P12:**\n   ```bash\n   # Export each certificate from Keychain Access:\n   # - Right-click certificate → Export\n   # - Choose .p12 format\n   # - Set a strong password (will be MACOS_CERTIFICATE_PASSWORD)\n\n   # If you have both in separate .p12 files, you can import them together\n   # or export them together from Keychain Access by selecting both\n\n   # Base64-encode for GitHub secrets:\n   base64 -i certificates.p12 | pbcopy\n   # Paste as MACOS_CERTIFICATE_P12\n   ```\n\n5. **Create app-specific password for notarization:**\n   - Go to [appleid.apple.com](https://appleid.apple.com/) → Sign-In and Security → App-Specific Passwords\n   - Generate a new password with a descriptive name (e.g., \"GitHub Actions Notarization\")\n   - Copy the generated password → `MACOS_NOTARIZE_PASSWORD`\n   - Use your Apple ID email → `MACOS_NOTARIZE_APPLE_ID`\n\n#### Testing Locally\n\nTo test signing locally before configuring GitHub secrets:\n\n```bash\n# Set environment variables\nexport MACOS_SIGN_APPLICATION=\"Developer ID Application: Your Name (TEAMID)\"\nexport MACOS_SIGN_INSTALLER=\"Developer ID Installer: Your Name (TEAMID)\"\nexport MACOS_NOTARIZE_APPLE_ID=\"your@email.com\"\nexport MACOS_NOTARIZE_TEAM_ID=\"ABCD123456\"\nexport MACOS_NOTARIZE_PASSWORD=\"xxxx-xxxx-xxxx-xxxx\"\n\n# Build the signed and notarized package\ncd build-aux/pkg-installer\nVERSION=2.26.0 ./build-pkg.sh\n\n# Verify the signature\npkgutil --check-signature ../../build-output/Telepresence.pkg\nspctl --assess --type install ../../build-output/Telepresence.pkg\n```\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nThis file provides guidance for contributors and AI assistants working with this repository.\n\n## Project Overview\n\nTelepresence is a Kubernetes development tool that enables fast local development by connecting your local workstation to a Kubernetes cluster. It allows developers to run services locally while accessing cluster resources and intercepting traffic from the cluster to their local machine.\n\n## Git Workflow\n\n- Never commit directly to the `release/v2` branch. Always create a feature branch with a name following the pattern `username/topic` (e.g., `thallgren/fix-dns-resolution`).\n- All commits must be signed and signed-off (`git commit -s -S`).\n- Limit commit message subjects to 72 characters. Do not wrap subjects;\n  rewrite them shorter instead. Wrap commit message body lines at 72\n  characters by default unless preserving exact external text requires a\n  longer line.\n- **Always run `make lint` and fix every reported issue before pushing.** This is non-negotiable — CI runs the same linters and a push with lint errors wastes a CI cycle. If `make lint` finds problems, fix them in the appropriate commit (use `git commit --fixup=<sha>` followed by `GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash --gpg-sign <base>` to fold them in) before pushing.\n- Push the branch and create a pull request for review.\n- Always merge PRs with a merge commit (never squash or rebase).\n\n## Design Plans\n\nMajor work (multi-file changes, new features, refactors) starts with a written plan\nunder `docs/plans/<topic>/`, presented for review before implementation begins.\n\nA plan is scaffolding for review, not a lasting artifact. It is removed in the last\ncommit on the PR that implements it. By then, everything in the plan must have been\nimplemented and documented, so the plan no longer has a purpose.\n\n## Build Artifacts\n\nThe Open Source version of Telepresence consists of three artifacts:\n\n**Client-side (runs on developer workstation):**\n- **`telepresence` binary** - The same binary serves as CLI, user daemon, and root daemon.\n- **`telepresence` Docker image** - Used as both user and root daemon when running `telepresence connect --docker`.\n\n**Cluster-side (runs in Kubernetes):**\n- **`tel2` Docker image** - Used by the traffic-manager deployment and injected as traffic-agent sidecars.\n\n## Build Commands\n\n```bash\n# Set required environment variables\nexport TELEPRESENCE_VERSION=v2.x.x-alpha.0  # or use auto-generated version\nexport TELEPRESENCE_REGISTRY=local          # 'local' for Docker Desktop, or 'ghcr.io/telepresenceio'\n\n# Build the telepresence binary\nmake build\n\n# Build Docker images (for local Kubernetes development)\nmake client-image    # Client container image\nmake tel2-image      # Traffic-manager/traffic-agent image\n\n# Build everything for local development\nmake build client-image tel2-image\n\n# Install to system\nmake install\n\n# Clean build artifacts\nmake clean\nmake clobber  # Also removes tools\n```\n\nEnvironment variables:\n- `TELEPRESENCE_REGISTRY` (required) - Docker registry for images. Use `local` for docker-based Kubernetes, or `ghcr.io/telepresenceio` for the release registry.\n- `TELEPRESENCE_VERSION` (optional) - Version string to compile into binaries and images. If not set, auto-generated from CHANGELOG.yml and source hash.\n\nRun `make help` for more information.\n\n### Building on Windows\n\nWindows builds use `build-aux\\winmake.bat` instead of `make` directly. Pass the same parameters as you would to make. The script runs make inside a Docker container with appropriate parameters for Windows binaries.\n\n## Testing\n\n```bash\n# Unit tests\nmake check-unit\n\n# Regression tests (requires a Kubernetes cluster; see the guide below)\nmake check-regression\n\n# One regression area / suite / test — plain go test selection:\ngo test ./regression_test -run 'TestIntercept/HeaderFilter/Test_PathPrefix'\n\n# Chart-value combinations, clusterless:\ngo test ./regression_test/golden\n```\n\nThe regression suite in `regression_test/` is the integration-test\npackage: declarative memoized fixtures, warm-cluster adoption for fast\nscoped runs, coverage instrumentation, and a bidirectional\ncompatibility subset. **Read `regression_test/README.md` before writing or\ndebugging these tests** — it documents the fixture engine's rules (lazy\naccessors, Mutate discipline, spec declarations), the RTEST_* environment,\nthe manager/workload catalogs, labels and platform constraints, coverage,\nand compat runs.\n\n## Linting\n\n```bash\n# Run all linters\nmake lint\n\n# Run Go linter only\nmake lint-go\n\n# Run protobuf linter only\nmake lint-rpc\n\n# Run documentation linter only (link/nav consistency via tools/src/docslint,\n# terminology and stale references via Vale in Docker; config in .vale.ini)\nmake lint-docs\n\n# Auto-fix lint issues\nmake format\n```\n\nLinting uses golangci-lint v2 running in Docker. Configuration is in `.golangci.yml`.\n\n## Code Comments\n\nComments must describe the code as it is. Never write comments that describe a\ntransition — why code was moved, what it replaced, or how it differs from an\nearlier version. The reader sees only the current code, so such comments carry\nno information for them. Keep comments short; avoid long explanations.\n\nOn internal (unexported) functions and methods, keep doc comments minimal: a\nfew lines stating only what the code cannot show, such as a locking-order or\npublication-order invariant. With well-named code, the details live in the\ncode itself; a reader who wants them will read it. Multi-paragraph comments\nthat justify design decisions belong in review discussions, not in the\nsource.\n\n## Code Generation\n\n```bash\n# Regenerate protobuf and license files\nmake generate\n\n# Regenerate protobuf files only\nmake protoc\n\n# Regenerate documentation files (after changing CHANGELOG.yml)\nmake docs-files\n```\n\n**Important:** After modifying `CHANGELOG.yml`, always run `make docs-files` to regenerate documentation files (`docs/release-notes.md`, `docs/release-notes.mdx`, `docs/variables.yml`).\n\n**Important:** All files under `docs/reference/cli/` are generated from Go source code. Do not edit them directly; instead, modify the corresponding Go source and regenerate.\n\n### Updating License Documentation\n\nRun `make generate` and commit changes to `DEPENDENCY_LICENSES.md` and `DEPENDENCIES.md`.\n\n## Documentation\n\nThe documentation under `docs/` aims to follow the\n[Diátaxis](https://diataxis.fr/) framework. Its four quadrants map to the\nlayout like this:\n\n| Diátaxis quadrant | Orientation | Location |\n|-------------------|-------------|----------|\n| Tutorials | learning | `docs/quick-start.md` |\n| How-to guides | task | `docs/howtos/` |\n| Reference | information | `docs/reference/` |\n| Explanation | understanding | `docs/concepts/` |\n\nWhen documenting a new feature, decide which quadrants it needs — typically a\nhow-to guide (how to enable/use it) plus a reference page (its complete\nbehavior, configuration, and limitations) — and keep the quadrants separate:\na how-to gets a task done and links to the reference for details; a reference\ndescribes exhaustively and doesn't teach. Add new pages to the navigation in\n`docs/doc-links.yml`, and run `make lint-docs` (link/nav consistency and\nterminology) before pushing.\n\n## Architecture\n\n### Main Components\n\n1. **CLI/Client** (`cmd/telepresence/`, `pkg/client/cli/`)\n   - Single binary serving as CLI, user daemon, and root daemon\n   - Commands are in `pkg/client/cli/cmd/`\n\n2. **User Daemon (userd)** (`pkg/client/userd/`)\n   - Runs as the user, manages connection to traffic-manager\n   - Handles intercepts, port forwards, cluster communication\n\n3. **Root Daemon (rootd)** (`pkg/client/rootd/`)\n   - Runs with elevated privileges\n   - Manages virtual network interface (VIF) and DNS\n\n4. **Traffic Manager** (`cmd/traffic/cmd/manager/`)\n   - Runs in the Kubernetes cluster (ambassador namespace by default)\n   - Coordinates intercepts between clients and traffic-agents\n\n5. **Traffic Agent** (`cmd/traffic/cmd/agent/`)\n   - Injected as sidecar into intercepted pods\n   - Routes traffic between the pod and the local machine\n\n6. **Agent Init** (`cmd/traffic/cmd/agentinit/`)\n   - Init container for setting up iptables rules in pods\n\n7. **Docker Network Driver** (`cmd/teleroute/`)\n   - Only used when connecting with `--docker` flag\n   - Provides the Docker network that enables communication between the Telepresence daemon container and other containers\n\n### Key Packages\n\n- `pkg/vif/` - Virtual network interface implementation\n- `pkg/tunnel/` - gRPC-based tunneling for network traffic\n- `pkg/dnsproxy/` - DNS resolution and proxying\n- `pkg/agentconfig/` - Traffic-agent configuration\n- `pkg/client/k8s/` - Kubernetes client interactions\n- `pkg/routing/` - Network routing logic\n- `pkg/client/cli/cmd/` - CLI commands. One per file.\n\n### RPC Definitions\n\nProtocol buffers are in `rpc/` with separate packages:\n- `rpc/connector/` - Client-to-userd communication\n- `rpc/daemon/` - Client-to-rootd communication\n- `rpc/manager/` - Client/userd-to-traffic-manager communication\n- `rpc/agent/` - Traffic-manager-to-traffic-agent communication\n\n### Version Parity Between CLI and Daemons\n\nThe CLI never talks to a user or root daemon of a different version.\n`pkg/client/cli/connect/version_check.go` enforces this on every command\nthat reaches a daemon: the host user daemon and root daemon must match the\nclient version exactly, and a containerized user daemon must match on\nmajor.minor.patch. This means changes to `rpc/connector/` and `rpc/daemon/`\nnever need backward-compatibility fallbacks — a new RPC can be assumed to\nexist on the daemon side. Backward compatibility DOES matter for\n`rpc/manager/` and `rpc/agent/`, where the cluster side is upgraded\nindependently of the client.\n\n### Helm Chart\n\nThe traffic-manager Helm chart is in `charts/telepresence-oss/`.\n\n## Debugging and Troubleshooting\n\n### Log Files\n\nThere are three log files:\n- `connector.log` - Output from user daemon: traffic-manager interaction, intercepts, port forwards\n- `daemon.log` - Output from root daemon: networking changes on your workstation\n- `cli.log` - Output from the command line interface\n\nLocations:\n- macOS: `~/Library/Logs/telepresence/`\n- Linux: `~/.cache/telepresence/logs/`\n- Windows: `%USERPROFILE%\\AppData\\Local\\logs`\n\nLogs rotate daily. Use `tail -F <filename>` to watch rotating logs seamlessly.\n\n### Debugging Early-Initialization Errors\n\nIf daemons fail during early initialization before logfiles are set up, run them directly to see stderr output. The `--address` flag is mandatory:\n\n```bash\n# Run user daemon directly\ntelepresence userd --logfile - --address :8083\n\n# Run root daemon directly (requires sudo)\nsudo telepresence rootd --logfile - --address :8084\n```\n\n### Profiling the Daemons\n\nEnable [pprof](https://pkg.go.dev/net/http/pprof) profiling:\n\n```bash\ntelepresence quit -s\ntelepresence connect --userd-profiling-port 6060 --rootd-profiling-port 6061\n# Then browse http://localhost:6060/debug/pprof/\n```\n\n### Dumping Goroutine Stacks\n\nSend SIGQUIT to a daemon to dump goroutine stacks to its log file. On Windows, use profiling instead.\n\n### RBAC Testing\n\nTo test with limited RBAC privileges:\n\n```bash\nkubectl apply -f k8s/client_rbac.yaml\nkubectl get sa telepresence-test-developer -o \"jsonpath={.secrets[0].name}\"\n# Get the token from the secret and configure kubectl\nkubectl get secret <secret-name> -o \"jsonpath={.data.token}\" | base64 --decode\nkubectl config set-credentials telepresence-test-developer --token <token>\nkubectl config use-context telepresence-test-developer\n```\n\n## Releases\n\nTo create a release, set `TELEPRESENCE_VERSION` and run `make prepare-release`. This creates two annotated tags (`vX.Y.Z` and `rpc/vX.Y.Z`) and a commit updating go.mod references. Pushing the tags and branch triggers the release workflow.\n\n**Important:** A tag push publishes the release and cannot be taken back. Never push the tags directly after `make prepare-release`. Push only the branch, open a PR for it, and follow `/ship-release` (`.claude/skills/ship-release`), which drives the release PR's CI (including the required `regression` gate), creates the docs PR in the telepresence.io repository, and pushes the tags only after everything is green. The command blocks below show the mechanics, not the order.\n\n```bash\n# Test release (marked as pre-release, not promoted to latest)\nexport TELEPRESENCE_VERSION=v2.27.0-test.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# Release candidate\nexport TELEPRESENCE_VERSION=v2.27.0-rc.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# GA release (becomes \"latest\", updates Homebrew)\nexport TELEPRESENCE_VERSION=v2.27.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n```\n\nVersion formats:\n- `vX.Y.Z-test.N` - Test release (pre-release)\n- `vX.Y.Z-rc.N` - Release candidate (pre-release)\n- `vX.Y.Z` - GA release (marked as latest, triggers Homebrew update)\n\n### Changelog\n\nWhen adding entries to `CHANGELOG.yml` for an upcoming release:\n- Use `date: (TBD)` for unreleased versions\n- The `make prepare-release` command will set the actual date when `TELEPRESENCE_VERSION` is a GA version (e.g., `v2.27.0`)\n- After modifying `CHANGELOG.yml`, run `make docs-files` to regenerate documentation\n\n### Documentation Website\n\nThe documentation website at [telepresence.io](https://telepresence.io) is managed in the [telepresenceio/telepresence.io](https://github.com/telepresenceio/telepresence.io) repository. When creating a GA release, update the website by running `make generate-version` in that repository with:\n- `DOCS_BRANCH` - Branch in this repository containing the docs (e.g., `release/v2`)\n- `DOCS_VERSION` - Major.minor version to generate or update (e.g., `2.27`)\n\nSee the telepresence.io repository for full instructions.\n\n### macOS Installer Signing and Notarization\n\nThe macOS `.pkg` installers are signed and notarized to pass Gatekeeper verification. The signing process uses a protected GitHub Environment to secure the signing credentials.\n\n#### Environment Setup\n\nThe `build-macos-pkg` job uses the `macos-signing` environment, which must be configured in the repository settings:\n\n1. Go to https://github.com/telepresenceio/telepresence/settings/environments\n2. Create an environment named `macos-signing`\n3. Enable \"Required reviewers\" and add authorized personnel\n4. Optionally restrict deployment branches to `release/*`\n5. Add the following secrets to the environment (not repository-level):\n\n| Secret Name | Description |\n|-------------|-------------|\n| `MACOS_CERTIFICATE_P12` | Base64-encoded P12 file containing both Application and Developer ID Installer certificates |\n| `MACOS_CERTIFICATE_PASSWORD` | Password for the P12 file |\n| `MACOS_SIGN_APPLICATION` | Developer ID Application certificate name (e.g., `Developer ID Application: Your Name (TEAMID)`) |\n| `MACOS_SIGN_INSTALLER` | Developer ID Installer certificate name (e.g., `Developer ID Installer: Your Name (TEAMID)`) |\n| `MACOS_NOTARIZE_APPLE_ID` | Apple ID email for notarization |\n| `MACOS_NOTARIZE_TEAM_ID` | Apple Developer Team ID |\n| `MACOS_NOTARIZE_PASSWORD` | App-specific password for notarization |\n\n#### Release Workflow\n\nWhen a release tag is pushed:\n1. All platform binaries (Linux, Windows, macOS) are built immediately\n2. Linux `.deb`/`.rpm` and Windows `.exe` installers are built\n3. The release is published with all binaries and Linux/Windows installers\n4. The `build-macos-pkg` job waits for approval from a required reviewer\n5. Once approved, signed `.pkg` installers are built and added to the release\n\nThis design ensures:\n- **Emergency releases can proceed** without the signing approver being available (all binaries and Linux/Windows installers are released)\n- **Signing credentials are protected** by requiring explicit approval before they are exposed\n- **Signed packages are added later** when the approver reviews and approves the job\n\nIf the environment is not configured or never approved, the release will contain macOS standalone binaries but not `.pkg` installers.\n\n#### Obtaining the Certificates\n\nYou need an [Apple Developer Program](https://developer.apple.com/programs/) membership ($99/year) to obtain signing certificates.\n\n1. **Create certificates in Apple Developer Portal:**\n   - Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list)\n   - Click the + button to create a new certificate\n   - Create **Developer ID Application** certificate (for signing binaries)\n   - Create **Developer ID Installer** certificate (for signing .pkg files)\n   - Download both certificates and double-click to install in Keychain Access\n\n2. **Find your Team ID:**\n   - Go to [Membership Details](https://developer.apple.com/account#MembershipDetailsCard)\n   - Copy the Team ID (10-character alphanumeric string)\n   - Set as `MACOS_NOTARIZE_TEAM_ID`\n\n3. **Find the certificate names:**\n   - Open Keychain Access and look under \"My Certificates\"\n   - The names will be like:\n     - `Developer ID Application: Your Name (TEAMID)` → `MACOS_SIGN_APPLICATION`\n     - `Developer ID Installer: Your Name (TEAMID)` → `MACOS_SIGN_INSTALLER`\n   - You can also list them with: `security find-identity -v -p codesigning`\n\n4. **Export certificates to P12:**\n   ```bash\n   # Export each certificate from Keychain Access:\n   # - Right-click certificate → Export\n   # - Choose .p12 format\n   # - Set a strong password (will be MACOS_CERTIFICATE_PASSWORD)\n\n   # If you have both in separate .p12 files, you can import them together\n   # or export them together from Keychain Access by selecting both\n\n   # Base64-encode for GitHub secrets:\n   base64 -i certificates.p12 | pbcopy\n   # Paste as MACOS_CERTIFICATE_P12\n   ```\n\n5. **Create app-specific password for notarization:**\n   - Go to [appleid.apple.com](https://appleid.apple.com/) → Sign-In and Security → App-Specific Passwords\n   - Generate a new password with a descriptive name (e.g., \"GitHub Actions Notarization\")\n   - Copy the generated password → `MACOS_NOTARIZE_PASSWORD`\n   - Use your Apple ID email → `MACOS_NOTARIZE_APPLE_ID`\n\n#### Testing Locally\n\nTo test signing locally before configuring GitHub secrets:\n\n```bash\n# Set environment variables\nexport MACOS_SIGN_APPLICATION=\"Developer ID Application: Your Name (TEAMID)\"\nexport MACOS_SIGN_INSTALLER=\"Developer ID Installer: Your Name (TEAMID)\"\nexport MACOS_NOTARIZE_APPLE_ID=\"your@email.com\"\nexport MACOS_NOTARIZE_TEAM_ID=\"ABCD123456\"\nexport MACOS_NOTARIZE_PASSWORD=\"xxxx-xxxx-xxxx-xxxx\"\n\n# Build the signed and notarized package\ncd build-aux/pkg-installer\nVERSION=2.26.0 ./build-pkg.sh\n\n# Verify the signature\npkgutil --check-signature ../../build-output/Telepresence.pkg\nspctl --assess --type install ../../build-output/Telepresence.pkg\n```\n","isInternal":false,"tokens":4432,"sizeBytes":18887},{"name":"openai.yaml","path":".claude/skills/changelog-entry/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/skills/changelog-entry/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"policy:\n  allow_implicit_invocation: true\n","isInternal":false,"tokens":11,"sizeBytes":42},{"name":"SKILL.md","path":".claude/skills/changelog-entry/SKILL.md","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/skills/changelog-entry/SKILL.md","title":"changelog-entry","category":"anthropic-skill","format":"markdown","content":"---\nname: changelog-entry\ndescription: Add a new entry to CHANGELOG.yml under the current unreleased version (or create the version block if needed), then regenerate documentation. Use when the user says things like \"add a changelog entry\", \"log this fix in the changelog\", or \"/changelog-entry\".\n---\n\n# changelog-entry\n\nAdds an entry to `CHANGELOG.yml` following the schema documented in the file header, then regenerates the derived documentation.\n\n## Inputs to gather (in order)\n\n1. **type** — one of `bugfix`, `feature`, `security`, `change`. If the user describes the change but does not pick a type, infer it:\n   - \"fixes/resolves/closes a bug\" → `bugfix`\n   - \"adds support for / introduces / new\" → `feature`\n   - \"CVE / vulnerability / hardens\" → `security`\n   - anything else affecting behavior → `change`\n2. **title** — short (≤80 chars), sentence-cased, no trailing period.\n3. **body** — 2-3 sentences. **This field is HTML, not markdown.** Use `<code>...</code>` for code, `<a href=\"...\">...</a>` for links. Prefer YAML's `>-` folded scalar so line wrapping doesn't leak literal newlines.\n4. **docs** *(optional)* — path to a docs page under `docs/` if the entry deserves a \"Learn more\" link.\n5. **image** *(optional)* — path under the `release-notes` directory if there's a visual.\n\n## Steps\n\n1. Read the top of `CHANGELOG.yml`. The first `items:` entry is the current/upcoming version.\n2. If it has `date: (TBD)`, append the new entry to its `notes:` array.\n3. If the top item is already dated (a shipped release), insert a NEW `- version: <next>` block above it with `date: (TBD)` and the single new note. Ask the user for the next version number — do not invent it.\n4. Match existing indentation exactly (2 spaces). YAML is whitespace-sensitive.\n5. After saving, run `make docs-files` to regenerate `docs/release-notes.md`, `docs/release-notes.mdx`, and `docs/variables.yml`. (The PostToolUse hook in `.claude/settings.json` will also try to do this; running it explicitly here makes the success/failure visible.)\n6. Show the user the diff: `git diff CHANGELOG.yml docs/release-notes.md docs/release-notes.mdx docs/variables.yml`.\n\n## Schema reference (from CHANGELOG.yml header)\n\n```yaml\nitems:\n  - version: 2.28.0\n    date: (TBD)              # or YYYY-MM-DD\n    notes:\n      - type: bugfix         # bugfix | feature | security | change\n        title: Short title\n        body: >-\n          Two or three sentences describing the change and why it\n          is noteworthy.  This is HTML.\n        docs: optional/path\n        image: optional/path\n```\n\n## Things to avoid\n\n- Do not edit `docs/release-notes.md`, `docs/release-notes.mdx`, or `docs/variables.yml` directly — they are generated.\n- Do not include markdown syntax in `body`; it is rendered as HTML.\n- Do not set `date:` to a concrete date for upcoming versions; `make prepare-release` does that automatically for GA versions.\n","frontmatter":{"name":"changelog-entry","description":"Add a new entry to CHANGELOG.yml under the current unreleased version (or create the version block if needed), then regenerate documentation. Use when the user says things like \"add a changelog entry\", \"log this fix in the changelog\", or \"/changelog-entry\"."},"isInternal":false,"tokens":730,"sizeBytes":2928},{"name":"openai.yaml","path":".claude/skills/prepare-release/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/skills/prepare-release/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"policy:\n  allow_implicit_invocation: false\n","isInternal":false,"tokens":11,"sizeBytes":43},{"name":"SKILL.md","path":".claude/skills/prepare-release/SKILL.md","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/skills/prepare-release/SKILL.md","title":"prepare-release","category":"anthropic-skill","format":"markdown","content":"---\nname: prepare-release\ndescription: Create the local release commit and tags by setting TELEPRESENCE_VERSION and running make prepare-release. Stops at the local commit+tags - pushing is the ship-release skill's job. Use when the user explicitly asks to prepare a release, RC, or test build. User-only.\ndisable-model-invocation: true\n---\n\n# prepare-release\n\nWraps the `make prepare-release` step so the local-tag-creation portion of a release is one explicit user action, not a chain of remembered commands. Stops at local tags; the `ship-release` skill takes over from there.\n\nThis is **user-only** by design (`disable-model-invocation: true`). The tags this skill creates will eventually drive a public release, so creating them must be an explicit user decision — never a side-effect of Claude inferring intent.\n\n## Confirm before doing anything\n\nAsk the user explicitly:\n\n1. **Version string** (`TELEPRESENCE_VERSION`) — must be one of:\n   - `vX.Y.Z-test.N` — pre-release, no Homebrew, no \"latest\"\n   - `vX.Y.Z-rc.N` — pre-release, no Homebrew, no \"latest\"\n   - `vX.Y.Z` — GA, marked latest, triggers Homebrew update\n2. **Branch** — should be a release branch (typically `release/v2`). Refuse to proceed from `main`-style branches.\n3. **Working tree** — must be clean. Run `git status`; if there are uncommitted or untracked files relevant to the build, stop.\n4. **CHANGELOG.yml status** — the top entry should have version matching `TELEPRESENCE_VERSION` (without the leading `v`). If it's still `date: (TBD)`, that's expected: `make prepare-release` sets the date for GA versions.\n\nShow all four checks to the user before running anything. Wait for explicit \"go\".\n\n## Steps\n\n```bash\nexport TELEPRESENCE_VERSION=vX.Y.Z[-suffix.N]\nmake prepare-release\n```\n\nThis creates:\n- An annotated tag `vX.Y.Z[-suffix.N]`\n- An annotated tag `rpc/vX.Y.Z[-suffix.N]`\n- A commit that bumps go.mod references inside the repo\n\nVerify with:\n\n```bash\ngit log -1 --stat\ngit tag --points-at HEAD\n```\n\n## Next: hand off to `ship-release`\n\nThis skill stops here, with the local commit and the two annotated tags. **Do not push anything.** To carry the release through CI, the docs PR, the Releases workflow, and the PR merges, invoke the `ship-release` skill (after pushing the branch and opening a PR on it — that's a manual handoff step the user does between the two skills).\n\n## Refuse to\n\n- Push anything (branch, commit, or tags). That's `ship-release`'s job.\n- Skip `make prepare-release` and just tag manually. The make target updates go.mod references; manual tagging skips that and ships a broken module.\n- Re-run `make prepare-release` on top of a previous attempt without first cleaning up the leftover tags. If `git tag --points-at HEAD` already lists the target tag, stop and report — the user has to decide whether to delete it.\n","frontmatter":{"name":"prepare-release","description":"Create the local release commit and tags by setting TELEPRESENCE_VERSION and running make prepare-release. Stops at the local commit+tags - pushing is the ship-release skill's job. Use when the user explicitly asks to prepare a release, RC, or test build. User-only.","disable-model-invocation":true,"disableModelInvocation":true},"isInternal":false,"tokens":691,"sizeBytes":2847},{"name":"openai.yaml","path":".claude/skills/regression-tests/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/skills/regression-tests/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"policy:\n  allow_implicit_invocation: true\n","isInternal":false,"tokens":11,"sizeBytes":42},{"name":"SKILL.md","path":".claude/skills/regression-tests/SKILL.md","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/skills/regression-tests/SKILL.md","title":"regression-tests","category":"anthropic-skill","format":"markdown","content":"---\nname: regression-tests\ndescription: Run, scope, or debug telepresence regression tests under regression_test/ — the integration-level suite. Use when the user wants to run an area, suite, or single test, debug a failure, or says \"/regression-tests\". Runs `go test ./regression_test` scoped with -run, in the background, writing to a log file so heavy output stays out of context.\n---\n\n# regression-tests\n\nRuns the telepresence regression suite from the main conversation, where this\nharness's shell-env quirks are known.\n\n## Background to assume\n\n- Tests live under `regression_test/` and need a working k8s cluster (kind /\n  minikube / Docker Desktop) plus images it can reach. For a local cluster set\n  `RTEST_REGISTRY=local` and LOAD the images into it rather than pushing.\n- `regression_test/README.md` is the reference: fixture-engine rules, the\n  RTEST_* table, catalogs, labels, coverage. Read it before debugging a\n  fixture problem.\n- **The shell environment always wins; there is no config file.** That means a\n  stale `export` in the user's shell silently changes a run.\n\n## Scoping: plain `go test -run`\n\nAreas are ordinary Go tests, suites and methods are subtests, so one `-run`\nexpression selects at any depth:\n\n```\ngo test ./regression_test -run '^TestIntercept$'\ngo test ./regression_test -run '^TestIntercept$/^HeaderFilter$'\ngo test ./regression_test -run '^TestIntercept$/^HeaderFilter$/^Test_PathPrefix$'\n```\n\nThere is no `TEST_SUITE`/`TEST_NAME` indirection and no make-argument dance:\ninline `VAR=value` prefixes work in this harness, so run `go test` directly\nand keep `make check-regression` for the full unscoped suite.\n\nAlways pass `-count=1` (results must never come from the test cache) and a\n`-timeout` that fits the scope: minutes for one suite, `-timeout=100m` for a\nfull run.\n\n## The run command\n\n```\nTELEPRESENCE_REGISTRY=local TELEPRESENCE_VERSION=<version> \\\n  RTEST_CONTEXT=<context> RTEST_TEARDOWN=1 \\\n  go test -count=1 -timeout=30m -run '^TestArea$/^Suite$' ./regression_test \\\n  > /tmp/rtest-suite.log 2>&1\n```\n\n- `RTEST_CONTEXT` pins the kube context. Pin it explicitly whenever the\n  machine has more than one cluster — the default is the kubeconfig's current\n  context, which is one `kubectx` away from being the wrong cluster.\n- `RTEST_TEARDOWN=1` destroys the run's resources at the end. Without it, dev\n  mode keeps namespaces, the release, and the connection for the next run to\n  adopt, which is what makes a scoped rerun take seconds.\n- `RTEST_FRESH=1` ignores adoptable resources (use when a previous run left\n  something suspect). CI implies fresh + teardown.\n- `RTEST_LABELS` / `RTEST_SKIP_LABELS` select on `compat-core`, `slow`,\n  `stress`, `flaky-retry`.\n\n## CRITICAL: a stale TELEPRESENCE_VERSION silently poisons the build\n\nThe version under test is read from the binary itself, and `make build` stamps\nit from `TELEPRESENCE_VERSION`. If the user's shell exports an old value, the\nbinary gets that version, the manager image tag no longer matches, and the run\nfails at image pull with a version that appears nowhere in your command.\n\nAlways pass `TELEPRESENCE_VERSION` explicitly to BOTH `make build` and the\n`go test` invocation, with the same value.\n\n## Rebuild before running\n\nThe suite runs the prebuilt binary plus the cluster-side images, so rebuild\nwhatever changed:\n\n- client-side Go (`pkg/`, `cmd/telepresence`) **and any `charts/` change**:\n  `make build` — the chart is go:embedded in the client binary, so a\n  chart-only edit without a rebuild silently installs the OLD chart.\n- manager / agent (`cmd/traffic`, `charts/`): `make load-images` (or\n  `make load-tel2-image`), so the cluster gets the new image.\n- `--docker` tests: `make client-image` — the daemon container runs on the\n  workstation, so it only needs to exist locally.\n\n`RTEST_REGISTRY=local` makes the manager use `pullPolicy=Never`. Reserve\n`make push-images` and a real registry for a remote cluster.\n\n## Keep heavy output out of context\n\nThis runs in the main thread, so do NOT Read or `tail` the whole log.\n\n1. Launch with `run_in_background: true`, redirecting to a fresh path.\n2. On completion, read only the summary:\n   `grep -E 'passed,|^ok|^--- FAIL|^FAIL' /tmp/rtest-suite.log | tail -5`\n   The runner's own last line is\n   `[rtest] run <stamp>: N passed, N failed, N skipped (N fixture actions)`.\n3. For a failure, the per-test artifacts are under\n   `build-output/rtest/logs/<stamp>/<TestPath>/` (cli.log, daemon logs), and\n   `manifest.json` records every test's outcome and labels.\n\n## Leftover state\n\n- `make rtest-clean` removes everything the framework created, in the cluster\n  and locally.\n- A daemon from a killed run: `telepresence quit -s`.\n- One rtest run per cluster at a time — resource names are stable by design,\n  which is what makes adoption work.\n- A leftover manager from unrelated work (\"traffic-manager in namespace X\n  already manages namespace Y\") blocks an unrestricted install: find it with\n  `kubectl get secret -A -l owner=helm`, and remember the chart also leaves\n  cluster-scoped resources (webhook config, ClusterRole/Binding) that\n  namespace deletion does not remove.\n\n## Workflow\n\n1. **Identify** the area/suite: areas are directories under\n   `regression_test/suites/`, and each suite is an `rt.Suite` type registered\n   in its `init()`.\n2. **Decide the rebuild** (see above) and run it with an explicit\n   `TELEPRESENCE_VERSION`.\n3. **Run scoped**, in the background, to a fresh log path.\n4. **Summarize:** the command, pass/fail/skip counts, failing test names, the\n   smallest excerpt explaining each failure, and the next concrete action.\n\n## Don't\n\n- Don't run the full suite unscoped without explicit user instruction — it is\n  roughly an hour serial; `make check-regression SHARD=1|2|3` runs a third of\n  it (the shard/area mapping lives in build-aux/main.mk).\n- Don't run `go test -list` or a deliberately non-matching `-run` to \"check\n  what exists\": the harness provisions real cluster resources before selection,\n  so it costs a full setup cycle. Grep the suite files instead.\n- Don't `make clobber` or destroy local images without asking.\n- Don't edit generated files: `docs/reference/cli/**`, `DEPENDENCIES.md`,\n  `DEPENDENCY_LICENSES.md`, `docs/release-notes*`.\n","frontmatter":{"name":"regression-tests","description":"Run, scope, or debug telepresence regression tests under regression_test/ — the integration-level suite. Use when the user wants to run an area, suite, or single test, debug a failure, or says \"/regression-tests\". Runs `go test ./regression_test` scoped with -run, in the background, writing to a log file so heavy output stays out of context."},"isInternal":false,"tokens":1601,"sizeBytes":6273},{"name":"openai.yaml","path":".claude/skills/ship-release/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/skills/ship-release/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"policy:\n  allow_implicit_invocation: false\n","isInternal":false,"tokens":11,"sizeBytes":43},{"name":"SKILL.md","path":".claude/skills/ship-release/SKILL.md","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/skills/ship-release/SKILL.md","title":"ship-release","category":"anthropic-skill","format":"markdown","content":"---\nname: ship-release\ndescription: Drive a Telepresence release from a prepared branch all the way through CI, docs, the Releases workflow, and PR merges. Assumes `make prepare-release` has already been run locally and the branch with that commit was pushed and a PR opened. Use when the user says \"ship the release\", or \"complete the release\". User-only.\ndisable-model-invocation: true\n---\n\n# ship-release\n\nEnd-to-end driver for releasing Telepresence. Picks up where `prepare-release` left off and carries the change through:\n\n1. Telepresence release PR (CI green + `regression` green)\n2. Docs PR in `../telepresence.io`\n3. Tag push, Releases workflow, merge of both PRs.\n\nThis is **user-only** (`disable-model-invocation: true`). A release is publicly visible and partially irreversible (tags push to GitHub, Homebrew updates for GA). Claude must never invoke this on its own.\n\n## Preconditions to verify before doing anything\n\nRun each check and stop with a clear message if any fails:\n\n1. **CWD is the telepresence repo.** `git rev-parse --show-toplevel` ends in `telepresence`.\n2. **`make prepare-release` has been run.** The current HEAD must carry both `vX.Y.Z` and `rpc/vX.Y.Z` annotated tags locally:\n   ```\n   git tag --points-at HEAD | sort\n   ```\n   Two entries expected. If the tag list is empty or missing the `rpc/` peer, stop — the user needs to run `make prepare-release` first.\n3. **Branch is pushed.** Capture `tp_branch=$(git branch --show-current)`, then:\n   ```\n   git ls-remote --exit-code origin \"refs/heads/$tp_branch\"\n   ```\n   If this fails, stop and tell the user to push the branch first.\n4. **PR exists.** `gh pr view \"$tp_branch\" --json number,state,url,headRefName`. If no PR, stop.\n5. **Sibling docs repo present.** `test -d ../telepresence.io && test -d ../telepresence.io/.git`. If not, stop.\n\nCapture once and reuse throughout:\n\n- `tp_branch` — name of the prepare-release branch.\n- `tp_version` — pick the non-`rpc/` tag from `git tag --points-at HEAD` (e.g. `v2.28.0`).\n- `docs_version` — `echo \"$tp_version\" | sed -E 's/^v([0-9]+\\.[0-9]+).*/\\1/'` (e.g. `2.28`).\n- `pr_number` — from `gh pr view`.\n\n## Phase 1 — Drive the Telepresence release PR\n\n### 1.1 Verify branch and PR (already done in preconditions)\n\n### 1.2 Wait for all checks except `regression` to be green\n\nUse:\n\n```\ngh pr checks \"$tp_branch\" --json name,state,conclusion\n```\n\nFilter out the rows whose name is exactly `regression` or `node_agent_docker_runtime` (neither has been triggered yet — the label triggers them). Match the name exactly: `regression_compat` is a different job, and it only runs behind the `compatibility test` label. For every remaining row:\n\n- `state == \"COMPLETED\"` and `conclusion == \"SUCCESS\"` → green\n- `conclusion ∈ {\"FAILURE\",\"CANCELLED\",\"TIMED_OUT\",\"ACTION_REQUIRED\"}` → **stop**. Report the failing check name and a short excerpt from `gh run view <run-id> --log-failed`. Do not advance.\n- Anything else (`IN_PROGRESS`, `QUEUED`, `PENDING`) → keep waiting.\n\n**Polling cadence:** these checks (lint, unit tests, license, image-scan) typically finish in 5-15 min. Use `ScheduleWakeup` with `delaySeconds=180` while any check is still running. Do not tight-loop with sleeps.\n\n### 1.3 Wait for `regression` to be green\n\n`regression` starts automatically with the push (the release branch lives in\nthis repository); there is nothing to trigger. If it needs another attempt,\nuse \"Re-run all jobs\" on its workflow run (`gh run rerun <run-id>`).\n\nThe regression suite runs as three parallel shards (~30 min including cluster setup), summed into the single `regression` context; the node-agent job runs beside them. Use `ScheduleWakeup` with `delaySeconds` around **900**. Poll with the same `gh pr checks` query, looking at the `regression` and `node_agent_docker_runtime` rows.\n\n- Success → continue to Phase 2.\n- Failure / cancellation → **stop and report**. Pull failed-step logs with `gh run view <run-id> --log-failed`.\n- Still running after ~90 minutes → tell the user and stop (workflow may be stuck).\n\n## Phase 2 — Create the docs PR\n\nDone in the sibling repo `../telepresence.io`. Each shell step below is a separate Bash call (no `&&` chains), and `cd` to switch repos.\n\n### 2.1 Pull master\n\n```\ncd ../telepresence.io\ngit checkout master\ngit pull\n```\n\n### 2.2 Create branch with the same name as the telepresence PR branch\n\n```\ngit checkout -b \"$tp_branch\"\n```\n\nIf that branch already exists locally from a previous attempt, stop and ask whether to reuse, reset, or rename.\n\n### 2.3 + 2.4 Export variables\n\n```\nexport DOCS_VERSION=\"$docs_version\"   # e.g. 2.28 — note: no patch number\nexport DOCS_BRANCH=\"$tp_branch\"\n```\n\n(Per CLAUDE.md, `export` in its own Bash call, then use in subsequent calls. Shell state persists between calls in a session.)\n\n### 2.5 Generate\n\n```\nmake generate-version\n```\n\n### 2.6 Verify output\n\n```\nls versioned_docs/version-\"$DOCS_VERSION\"\ngit status\n```\n\nExpectations:\n\n- **Minor release** (first time this `2.X` is generated): the directory `versioned_docs/version-$DOCS_VERSION/` appears as untracked.\n- **Bugfix release** (directory already existed): files within it are modified.\n\nIf `versioned_docs/version-$DOCS_VERSION` is absent or `git status` shows no changes, **stop and report** — `make generate-version` did not do anything useful.\n\n### 2.7 Build the site locally before pushing\n\nNetlify (the `deploy/netlify`, `Pages changed`, `Header rules`, `Redirect rules`\nchecks) and the `Check`/`Lint` GitHub jobs all run `yarn build` (docusaurus\nbuild). Run it locally first so a broken build is caught here, not after a\npush-and-wait CI cycle:\n\n```\n# If node_modules is absent: yarn install --frozen-lockfile\nyarn build\n```\n\n- Exit 0 → the production build (including the new `version-$DOCS_VERSION`)\n  compiled. Proceed to the PR.\n- Non-zero → **stop and do not push.** Read the error; it names the offending\n  file and line.\n\n**Most common failure: MDX parse error in `release-notes.mdx`.** A `.mdx` file\nis JSX, so literal `{` / `}` **inside an HTML element** like\n`<code>{cmd, stdout}</code>` are parsed as JS expressions and fail with\n`Could not parse expression with acorn`. (Braces inside Markdown backtick\nspans — `` `{tcp|udp}` `` — are safe.) These release-notes files are generated,\nso fix the **source**, not the generated copy:\n\n1. In the telepresence repo, edit the offending `CHANGELOG.yml` entry to remove\n   the literal braces (rephrase, e.g. `<code>cmd</code>/<code>stdout</code>`, or\n   move the snippet into a backtick span), then `make docs-files`.\n2. Commit + push that fix to the release branch (it belongs in the release PR).\n3. Back in the docs repo, re-run `make generate-version` to re-pull the fixed\n   docs, then `yarn build` again before continuing.\n\n### 2.8 Create the PR\n\n```\ngit add versioned_docs/version-\"$DOCS_VERSION\" versioned_sidebars docusaurus.config.js versions.json\n# (Add only the files that actually changed — git status will tell you which of the\n#  above moved; for a fresh minor you'll likely see all of them, for a bugfix only some.)\ngit commit -s -S -m \"Generate docs for telepresence $tp_version\"\ngit push -u origin \"$tp_branch\"\ngh pr create --base master --head \"$tp_branch\" \\\n  --title \"Generate docs for telepresence $tp_version\" \\\n  --body \"Generated with \\`make generate-version\\` DOCS_VERSION=$docs_version DOCS_BRANCH=$tp_branch.\"\n```\n\nCapture `docs_pr_number` from the `gh pr create` output URL.\n\nDo not include \"Co-Authored-By\" or \"Generated with\" trailers in the commit message or the PR body (per global preferences).\n\n### 2.9 Monitor the docs PR checks\n\n```\ngh pr checks \"$tp_branch\" --json name,state,conclusion\n```\n\nSame polling rules as Phase 1.2. If anything fails, **stop and report**.\n\n## Phase 3 — Release\n\n`cd` back to the telepresence repo for step 3.1 and 3.3a.\n\n### 3.1 Push the release tags\n\n```\ngit push origin \"$tp_version\" \"rpc/$tp_version\"\n```\n\nThis triggers the **Releases** workflow (`.github/workflows/release.yaml`). The release PR is still unmerged at this point — that is intentional. Merging now would create a new commit and move the branch tip away from the tagged commit.\n\n### 3.2 Monitor the Releases workflow\n\n```\ngh run list --workflow=release.yaml --limit 1 --json databaseId,status,conclusion,url\ngh run view <id> --json jobs\n```\n\nThe workflow requires manual approval of a protected GitHub environment (`macos-signing`) containing secrets for macOS signing. Wait for this up to **24 hours**. Poll with `ScheduleWakeup` at `delaySeconds=1800` (or longer when overnight). Surface the workflow URL early so the user can chase the approver.\n\n- If the workflow completes successfully → continue to 3.3.\n- If a job other than `build-macos-pkg` fails → **stop and report**.\n- If `build-macos-pkg` itself is never approved within 24h → tell the user; per CLAUDE.md the release still ships without `.pkg` installers, and the user can decide whether to proceed to 3.3 anyway.\n\n### 3.3 Merge both PRs — **GA versions only**\n\n**For pre-release versions (`-test.N`, `-rc.N`): skip this step entirely and\nstop here.** Both PRs stay open until the GA release ships: the release\nbranch accumulates the rc and GA prepare-release commits and merges once,\nafter GA, and the docs PR must not publish the new version's docs on\ntelepresence.io before GA exists (regenerate it from the GA branch before\nmerging). The rc's GitHub pre-release and its tags are the only public\nartifacts of a pre-release ship.\n\nFor a GA version: order does not matter. Both must use a **merge commit**\n(CLAUDE.md: never squash, never rebase).\n\n```\n# telepresence PR (in telepresence repo)\ngh pr merge \"$tp_branch\" --merge\n\n# docs PR (in ../telepresence.io)\ncd ../telepresence.io\ngh pr merge \"$tp_branch\" --merge\n```\n\nVerify each merged: `gh pr view \"$tp_branch\" --json state` should report `MERGED`.\n\n## Long-wait strategy\n\n- Anything under 5 min → don't sleep; just poll once.\n- 5-30 min waits (Phase 1.2 non-regression checks) → `ScheduleWakeup` with `delaySeconds=180`.\n- 30-60 min waits (Phase 1.3 `regression`) → `ScheduleWakeup` with `delaySeconds=1200`.\n- Hours-to-overnight (Phase 3.2 macOS signing approval) → `ScheduleWakeup` with `delaySeconds=1800` or longer.\n\nEach wake-up: re-fetch state, decide green/red/still-waiting, schedule the next wake or advance.\n\n## What \"stop and report\" means\n\n- Do not advance to the next numbered step.\n- Surface: the step that failed, the check/run name(s), the run URL(s), and a short excerpt from `gh run view <id> --log-failed`.\n- Do not retry automatically. Wait for the user to direct.\n- Do not delete branches, force-push, or close PRs. The user decides what to do.\n\n## What this skill must NEVER do\n\n- Run `make prepare-release` itself — that's a separate skill and a separate decision.\n- Push tags before all required PR checks are green (Phase 1 must complete first).\n- Merge the release PR or the docs PR for a pre-release (`-test.*`/`-rc.*`) version — both stay open until GA (see 3.3).\n- Merge PRs as squash or rebase — both repos require merge commits.\n- Trigger `regression` by any means other than the push itself or a re-run of its workflow run.\n- Approve the `macos-signing` environment programmatically — that requires a human reviewer.\n- Force-push or delete the release branch.\n","frontmatter":{"name":"ship-release","description":"Drive a Telepresence release from a prepared branch all the way through CI, docs, the Releases workflow, and PR merges. Assumes `make prepare-release` has already been run locally and the branch with that commit was pushed and a PR opened. Use when the user says \"ship the release\", or \"complete the release\". User-only.","disable-model-invocation":true,"disableModelInvocation":true},"isInternal":false,"tokens":2939,"sizeBytes":11342},{"name":"proto-rpc-reviewer.md","path":".claude/agents/proto-rpc-reviewer.md","rawUrl":"https://raw.githubusercontent.com/telepresenceio/telepresence/HEAD/.claude/agents/proto-rpc-reviewer.md","title":"proto-rpc-reviewer","category":"subagent-persona","format":"markdown","content":"---\nname: proto-rpc-reviewer\ndescription: Use when reviewing changes to any .proto file under rpc/ or to the Go bindings generated from them. Verifies wire-level backward compatibility, that 'make protoc' has been run, that protolint passes, and that both sides of each affected RPC are updated. Surfaces incompatibilities that would break older clients, older traffic-managers, or older traffic-agents talking to a new peer.\ntools: Read, Grep, Glob, Bash\n---\n\nYou are the gRPC contract reviewer for the telepresence repository.\n\n## Communication boundaries you must consider\n\nThe repo defines four RPC surfaces; a single proto edit can ripple across them:\n\n| Boundary                               | Proto package        |\n|----------------------------------------|----------------------|\n| client/userd ↔ traffic-manager         | `rpc/manager/`       |\n| client ↔ user daemon                   | `rpc/connector/`     |\n| client ↔ root daemon                   | `rpc/daemon/`        |\n| traffic-manager ↔ traffic-agent        | `rpc/agent/`         |\n| auth                                   | `rpc/authenticator/` |\n| teleroute (docker network driver)      | `rpc/teleroute/`     |\n| shared types                           | `rpc/common/`        |\n\nEach daemon ships independently: an older client may talk to a newer traffic-manager, a newer traffic-manager may inject an older traffic-agent (mismatched manifest), and a newer agent may run alongside an older sidecar in another pod. Wire compatibility is therefore mandatory, not optional.\n\n## Checks you must run\n\n1. **Wire compatibility:**\n   - Field numbers must never be reused or repurposed.\n   - Field types must not change (e.g., int32 → int64 silently corrupts).\n   - Enum values must not be renumbered; only appended.\n   - `optional` and `repeated` are part of the wire contract; do not flip.\n   - Removing a field requires `reserved` to lock the number/name.\n2. **Generated code is in sync:** Confirm `make protoc` has been run — check that .pb.go files in the same package are touched in the same change. If not, flag and recommend running it.\n3. **Lint:** Confirm `protolint` would pass against the configured rules in `.protolint.yaml` (line length 120, ENUM_FIELD_NAMES_PREFIX disabled). Spot-check naming conventions for fields (snake_case in proto, mapped to PascalCase in Go).\n4. **Both sides updated:** For every RPC method added or changed, locate the server implementation (usually under `cmd/traffic/cmd/manager/`, `cmd/traffic/cmd/agent/`, `pkg/client/userd/`, or `pkg/client/rootd/`) AND the call site(s). If only one side is touched, flag it.\n5. **Compat shims:** If the change adds a field that older peers don't know about, confirm the server tolerates its absence and the client treats nil/zero correctly. Reject any change that requires a synchronized upgrade of both sides.\n\n## Reporting format\n\nReturn a punch list, not prose. For each finding:\n\n- **Severity:** Blocker / Risk / Nit\n- **Where:** file:line\n- **Why:** one sentence\n- **Fix:** one sentence\n\nEnd with a one-line verdict: \"Safe to merge\", \"Needs follow-up\", or \"Blocked\".\n\n## What NOT to do\n\n- Do not edit any files. You are a reviewer.\n- Do not run `make protoc` yourself; report whether it appears to have been run and let the caller decide.\n- Do not chase code-style nits unrelated to the proto/RPC contract.\n","frontmatter":{"name":"proto-rpc-reviewer","description":"Use when reviewing changes to any .proto file under rpc/ or to the Go bindings generated from them. Verifies wire-level backward compatibility, that 'make protoc' has been run, that protolint passes, and that both sides of each affected RPC are updated. Surfaces incompatibilities that would break older clients, older traffic-managers, or older traffic-agents talking to a new peer.","tools":"Read, Grep, Glob, Bash"},"isInternal":false,"tokens":763,"sizeBytes":3372}],"systemPromptSnippet":"<agent_rules repository=\"telepresenceio/telepresence\">\n\n<!-- Skill/Rule: AI Agent Protocol & Instructions (AGENTS.md) -->\n# AGENTS.md\n\nThis file provides guidance for contributors and AI assistants working with this repository.\n\n## Project Overview\n\nTelepresence is a Kubernetes development tool that enables fast local development by connecting your local workstation to a Kubernetes cluster. It allows developers to run services locally while accessing cluster resources and intercepting traffic from the cluster to their local machine.\n\n## Git Workflow\n\n- Never commit directly to the `release/v2` branch. Always create a feature branch with a name following the pattern `username/topic` (e.g., `thallgren/fix-dns-resolution`).\n- All commits must be signed and signed-off (`git commit -s -S`).\n- Limit commit message subjects to 72 characters. Do not wrap subjects;\n  rewrite them shorter instead. Wrap commit message body lines at 72\n  characters by default unless preserving exact external text requires a\n  longer line.\n- **Always run `make lint` and fix every reported issue before pushing.** This is non-negotiable — CI runs the same linters and a push with lint errors wastes a CI cycle. If `make lint` finds problems, fix them in the appropriate commit (use `git commit --fixup=<sha>` followed by `GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash --gpg-sign <base>` to fold them in) before pushing.\n- Push the branch and create a pull request for review.\n- Always merge PRs with a merge commit (never squash or rebase).\n\n## Design Plans\n\nMajor work (multi-file changes, new features, refactors) starts with a written plan\nunder `docs/plans/<topic>/`, presented for review before implementation begins.\n\nA plan is scaffolding for review, not a lasting artifact. It is removed in the last\ncommit on the PR that implements it. By then, everything in the plan must have been\nimplemented and documented, so the plan no longer has a purpose.\n\n## Build Artifacts\n\nThe Open Source version of Telepresence consists of three artifacts:\n\n**Client-side (runs on developer workstation):**\n- **`telepresence` binary** - The same binary serves as CLI, user daemon, and root daemon.\n- **`telepresence` Docker image** - Used as both user and root daemon when running `telepresence connect --docker`.\n\n**Cluster-side (runs in Kubernetes):**\n- **`tel2` Docker image** - Used by the traffic-manager deployment and injected as traffic-agent sidecars.\n\n## Build Commands\n\n```bash\n# Set required environment variables\nexport TELEPRESENCE_VERSION=v2.x.x-alpha.0  # or use auto-generated version\nexport TELEPRESENCE_REGISTRY=local          # 'local' for Docker Desktop, or 'ghcr.io/telepresenceio'\n\n# Build the telepresence binary\nmake build\n\n# Build Docker images (for local Kubernetes development)\nmake client-image    # Client container image\nmake tel2-image      # Traffic-manager/traffic-agent image\n\n# Build everything for local development\nmake build client-image tel2-image\n\n# Install to system\nmake install\n\n# Clean build artifacts\nmake clean\nmake clobber  # Also removes tools\n```\n\nEnvironment variables:\n- `TELEPRESENCE_REGISTRY` (required) - Docker registry for images. Use `local` for docker-based Kubernetes, or `ghcr.io/telepresenceio` for the release registry.\n- `TELEPRESENCE_VERSION` (optional) - Version string to compile into binaries and images. If not set, auto-generated from CHANGELOG.yml and source hash.\n\nRun `make help` for more information.\n\n### Building on Windows\n\nWindows builds use `build-aux\\winmake.bat` instead of `make` directly. Pass the same parameters as you would to make. The script runs make inside a Docker container with appropriate parameters for Windows binaries.\n\n## Testing\n\n```bash\n# Unit tests\nmake check-unit\n\n# Regression tests (requires a Kubernetes cluster; see the guide below)\nmake check-regression\n\n# One regression area / suite / test — plain go test selection:\ngo test ./regression_test -run 'TestIntercept/HeaderFilter/Test_PathPrefix'\n\n# Chart-value combinations, clusterless:\ngo test ./regression_test/golden\n```\n\nThe regression suite in `regression_test/` is the integration-test\npackage: declarative memoized fixtures, warm-cluster adoption for fast\nscoped runs, coverage instrumentation, and a bidirectional\ncompatibility subset. **Read `regression_test/README.md` before writing or\ndebugging these tests** — it documents the fixture engine's rules (lazy\naccessors, Mutate discipline, spec declarations), the RTEST_* environment,\nthe manager/workload catalogs, labels and platform constraints, coverage,\nand compat runs.\n\n## Linting\n\n```bash\n# Run all linters\nmake lint\n\n# Run Go linter only\nmake lint-go\n\n# Run protobuf linter only\nmake lint-rpc\n\n# Run documentation linter only (link/nav consistency via tools/src/docslint,\n# terminology and stale references via Vale in Docker; config in .vale.ini)\nmake lint-docs\n\n# Auto-fix lint issues\nmake format\n```\n\nLinting uses golangci-lint v2 running in Docker. Configuration is in `.golangci.yml`.\n\n## Code Comments\n\nComments must describe the code as it is. Never write comments that describe a\ntransition — why code was moved, what it replaced, or how it differs from an\nearlier version. The reader sees only the current code, so such comments carry\nno information for them. Keep comments short; avoid long explanations.\n\nOn internal (unexported) functions and methods, keep doc comments minimal: a\nfew lines stating only what the code cannot show, such as a locking-order or\npublication-order invariant. With well-named code, the details live in the\ncode itself; a reader who wants them will read it. Multi-paragraph comments\nthat justify design decisions belong in review discussions, not in the\nsource.\n\n## Code Generation\n\n```bash\n# Regenerate protobuf and license files\nmake generate\n\n# Regenerate protobuf files only\nmake protoc\n\n# Regenerate documentation files (after changing CHANGELOG.yml)\nmake docs-files\n```\n\n**Important:** After modifying `CHANGELOG.yml`, always run `make docs-files` to regenerate documentation files (`docs/release-notes.md`, `docs/release-notes.mdx`, `docs/variables.yml`).\n\n**Important:** All files under `docs/reference/cli/` are generated from Go source code. Do not edit them directly; instead, modify the corresponding Go source and regenerate.\n\n### Updating License Documentation\n\nRun `make generate` and commit changes to `DEPENDENCY_LICENSES.md` and `DEPENDENCIES.md`.\n\n## Documentation\n\nThe documentation under `docs/` aims to follow the\n[Diátaxis](https://diataxis.fr/) framework. Its four quadrants map to the\nlayout like this:\n\n| Diátaxis quadrant | Orientation | Location |\n|-------------------|-------------|----------|\n| Tutorials | learning | `docs/quick-start.md` |\n| How-to guides | task | `docs/howtos/` |\n| Reference | information | `docs/reference/` |\n| Explanation | understanding | `docs/concepts/` |\n\nWhen documenting a new feature, decide which quadrants it needs — typically a\nhow-to guide (how to enable/use it) plus a reference page (its complete\nbehavior, configuration, and limitations) — and keep the quadrants separate:\na how-to gets a task done and links to the reference for details; a reference\ndescribes exhaustively and doesn't teach. Add new pages to the navigation in\n`docs/doc-links.yml`, and run `make lint-docs` (link/nav consistency and\nterminology) before pushing.\n\n## Architecture\n\n### Main Components\n\n1. **CLI/Client** (`cmd/telepresence/`, `pkg/client/cli/`)\n   - Single binary serving as CLI, user daemon, and root daemon\n   - Commands are in `pkg/client/cli/cmd/`\n\n2. **User Daemon (userd)** (`pkg/client/userd/`)\n   - Runs as the user, manages connection to traffic-manager\n   - Handles intercepts, port forwards, cluster communication\n\n3. **Root Daemon (rootd)** (`pkg/client/rootd/`)\n   - Runs with elevated privileges\n   - Manages virtual network interface (VIF) and DNS\n\n4. **Traffic Manager** (`cmd/traffic/cmd/manager/`)\n   - Runs in the Kubernetes cluster (ambassador namespace by default)\n   - Coordinates intercepts between clients and traffic-agents\n\n5. **Traffic Agent** (`cmd/traffic/cmd/agent/`)\n   - Injected as sidecar into intercepted pods\n   - Routes traffic between the pod and the local machine\n\n6. **Agent Init** (`cmd/traffic/cmd/agentinit/`)\n   - Init container for setting up iptables rules in pods\n\n7. **Docker Network Driver** (`cmd/teleroute/`)\n   - Only used when connecting with `--docker` flag\n   - Provides the Docker network that enables communication between the Telepresence daemon container and other containers\n\n### Key Packages\n\n- `pkg/vif/` - Virtual network interface implementation\n- `pkg/tunnel/` - gRPC-based tunneling for network traffic\n- `pkg/dnsproxy/` - DNS resolution and proxying\n- `pkg/agentconfig/` - Traffic-agent configuration\n- `pkg/client/k8s/` - Kubernetes client interactions\n- `pkg/routing/` - Network routing logic\n- `pkg/client/cli/cmd/` - CLI commands. One per file.\n\n### RPC Definitions\n\nProtocol buffers are in `rpc/` with separate packages:\n- `rpc/connector/` - Client-to-userd communication\n- `rpc/daemon/` - Client-to-rootd communication\n- `rpc/manager/` - Client/userd-to-traffic-manager communication\n- `rpc/agent/` - Traffic-manager-to-traffic-agent communication\n\n### Version Parity Between CLI and Daemons\n\nThe CLI never talks to a user or root daemon of a different version.\n`pkg/client/cli/connect/version_check.go` enforces this on every command\nthat reaches a daemon: the host user daemon and root daemon must match the\nclient version exactly, and a containerized user daemon must match on\nmajor.minor.patch. This means changes to `rpc/connector/` and `rpc/daemon/`\nnever need backward-compatibility fallbacks — a new RPC can be assumed to\nexist on the daemon side. Backward compatibility DOES matter for\n`rpc/manager/` and `rpc/agent/`, where the cluster side is upgraded\nindependently of the client.\n\n### Helm Chart\n\nThe traffic-manager Helm chart is in `charts/telepresence-oss/`.\n\n## Debugging and Troubleshooting\n\n### Log Files\n\nThere are three log files:\n- `connector.log` - Output from user daemon: traffic-manager interaction, intercepts, port forwards\n- `daemon.log` - Output from root daemon: networking changes on your workstation\n- `cli.log` - Output from the command line interface\n\nLocations:\n- macOS: `~/Library/Logs/telepresence/`\n- Linux: `~/.cache/telepresence/logs/`\n- Windows: `%USERPROFILE%\\AppData\\Local\\logs`\n\nLogs rotate daily. Use `tail -F <filename>` to watch rotating logs seamlessly.\n\n### Debugging Early-Initialization Errors\n\nIf daemons fail during early initialization before logfiles are set up, run them directly to see stderr output. The `--address` flag is mandatory:\n\n```bash\n# Run user daemon directly\ntelepresence userd --logfile - --address :8083\n\n# Run root daemon directly (requires sudo)\nsudo telepresence rootd --logfile - --address :8084\n```\n\n### Profiling the Daemons\n\nEnable [pprof](https://pkg.go.dev/net/http/pprof) profiling:\n\n```bash\ntelepresence quit -s\ntelepresence connect --userd-profiling-port 6060 --rootd-profiling-port 6061\n# Then browse http://localhost:6060/debug/pprof/\n```\n\n### Dumping Goroutine Stacks\n\nSend SIGQUIT to a daemon to dump goroutine stacks to its log file. On Windows, use profiling instead.\n\n### RBAC Testing\n\nTo test with limited RBAC privileges:\n\n```bash\nkubectl apply -f k8s/client_rbac.yaml\nkubectl get sa telepresence-test-developer -o \"jsonpath={.secrets[0].name}\"\n# Get the token from the secret and configure kubectl\nkubectl get secret <secret-name> -o \"jsonpath={.data.token}\" | base64 --decode\nkubectl config set-credentials telepresence-test-developer --token <token>\nkubectl config use-context telepresence-test-developer\n```\n\n## Releases\n\nTo create a release, set `TELEPRESENCE_VERSION` and run `make prepare-release`. This creates two annotated tags (`vX.Y.Z` and `rpc/vX.Y.Z`) and a commit updating go.mod references. Pushing the tags and branch triggers the release workflow.\n\n**Important:** A tag push publishes the release and cannot be taken back. Never push the tags directly after `make prepare-release`. Push only the branch, open a PR for it, and follow `/ship-release` (`.claude/skills/ship-release`), which drives the release PR's CI (including the required `regression` gate), creates the docs PR in the telepresence.io repository, and pushes the tags only after everything is green. The command blocks below show the mechanics, not the order.\n\n```bash\n# Test release (marked as pre-release, not promoted to latest)\nexport TELEPRESENCE_VERSION=v2.27.0-test.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# Release candidate\nexport TELEPRESENCE_VERSION=v2.27.0-rc.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# GA release (becomes \"latest\", updates Homebrew)\nexport TELEPRESENCE_VERSION=v2.27.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n```\n\nVersion formats:\n- `vX.Y.Z-test.N` - Test release (pre-release)\n- `vX.Y.Z-rc.N` - Release candidate (pre-release)\n- `vX.Y.Z` - GA release (marked as latest, triggers Homebrew update)\n\n### Changelog\n\nWhen adding entries to `CHANGELOG.yml` for an upcoming release:\n- Use `date: (TBD)` for unreleased versions\n- The `make prepare-release` command will set the actual date when `TELEPRESENCE_VERSION` is a GA version (e.g., `v2.27.0`)\n- After modifying `CHANGELOG.yml`, run `make docs-files` to regenerate documentation\n\n### Documentation Website\n\nThe documentation website at [telepresence.io](https://telepresence.io) is managed in the [telepresenceio/telepresence.io](https://github.com/telepresenceio/telepresence.io) repository. When creating a GA release, update the website by running `make generate-version` in that repository with:\n- `DOCS_BRANCH` - Branch in this repository containing the docs (e.g., `release/v2`)\n- `DOCS_VERSION` - Major.minor version to generate or update (e.g., `2.27`)\n\nSee the telepresence.io repository for full instructions.\n\n### macOS Installer Signing and Notarization\n\nThe macOS `.pkg` installers are signed and notarized to pass Gatekeeper verification. The signing process uses a protected GitHub Environment to secure the signing credentials.\n\n#### Environment Setup\n\nThe `build-macos-pkg` job uses the `macos-signing` environment, which must be configured in the repository settings:\n\n1. Go to https://github.com/telepresenceio/telepresence/settings/environments\n2. Create an environment named `macos-signing`\n3. Enable \"Required reviewers\" and add authorized personnel\n4. Optionally restrict deployment branches to `release/*`\n5. Add the following secrets to the environment (not repository-level):\n\n| Secret Name | Description |\n|-------------|-------------|\n| `MACOS_CERTIFICATE_P12` | Base64-encoded P12 file containing both Application and Developer ID Installer certificates |\n| `MACOS_CERTIFICATE_PASSWORD` | Password for the P12 file |\n| `MACOS_SIGN_APPLICATION` | Developer ID Application certificate name (e.g., `Developer ID Application: Your Name (TEAMID)`) |\n| `MACOS_SIGN_INSTALLER` | Developer ID Installer certificate name (e.g., `Developer ID Installer: Your Name (TEAMID)`) |\n| `MACOS_NOTARIZE_APPLE_ID` | Apple ID email for notarization |\n| `MACOS_NOTARIZE_TEAM_ID` | Apple Developer Team ID |\n| `MACOS_NOTARIZE_PASSWORD` | App-specific password for notarization |\n\n#### Release Workflow\n\nWhen a release tag is pushed:\n1. All platform binaries (Linux, Windows, macOS) are built immediately\n2. Linux `.deb`/`.rpm` and Windows `.exe` installers are built\n3. The release is published with all binaries and Linux/Windows installers\n4. The `build-macos-pkg` job waits for approval from a required reviewer\n5. Once approved, signed `.pkg` installers are built and added to the release\n\nThis design ensures:\n- **Emergency releases can proceed** without the signing approver being available (all binaries and Linux/Windows installers are released)\n- **Signing credentials are protected** by requiring explicit approval before they are exposed\n- **Signed packages are added later** when the approver reviews and approves the job\n\nIf the environment is not configured or never approved, the release will contain macOS standalone binaries but not `.pkg` installers.\n\n#### Obtaining the Certificates\n\nYou need an [Apple Developer Program](https://developer.apple.com/programs/) membership ($99/year) to obtain signing certificates.\n\n1. **Create certificates in Apple Developer Portal:**\n   - Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list)\n   - Click the + button to create a new certificate\n   - Create **Developer ID Application** certificate (for signing binaries)\n   - Create **Developer ID Installer** certificate (for signing .pkg files)\n   - Download both certificates and double-click to install in Keychain Access\n\n2. **Find your Team ID:**\n   - Go to [Membership Details](https://developer.apple.com/account#MembershipDetailsCard)\n   - Copy the Team ID (10-character alphanumeric string)\n   - Set as `MACOS_NOTARIZE_TEAM_ID`\n\n3. **Find the certificate names:**\n   - Open Keychain Access and look under \"My Certificates\"\n   - The names will be like:\n     - `Developer ID Application: Your Name (TEAMID)` → `MACOS_SIGN_APPLICATION`\n     - `Developer ID Installer: Your Name (TEAMID)` → `MACOS_SIGN_INSTALLER`\n   - You can also list them with: `security find-identity -v -p codesigning`\n\n4. **Export certificates to P12:**\n   ```bash\n   # Export each certificate from Keychain Access:\n   # - Right-click certificate → Export\n   # - Choose .p12 format\n   # - Set a strong password (will be MACOS_CERTIFICATE_PASSWORD)\n\n   # If you have both in separate .p12 files, you can import them together\n   # or export them together from Keychain Access by selecting both\n\n   # Base64-encode for GitHub secrets:\n   base64 -i certificates.p12 | pbcopy\n   # Paste as MACOS_CERTIFICATE_P12\n   ```\n\n5. **Create app-specific password for notarization:**\n   - Go to [appleid.apple.com](https://appleid.apple.com/) → Sign-In and Security → App-Specific Passwords\n   - Generate a new password with a descriptive name (e.g., \"GitHub Actions Notarization\")\n   - Copy the generated password → `MACOS_NOTARIZE_PASSWORD`\n   - Use your Apple ID email → `MACOS_NOTARIZE_APPLE_ID`\n\n#### Testing Locally\n\nTo test signing locally before configuring GitHub secrets:\n\n```bash\n# Set environment variables\nexport MACOS_SIGN_APPLICATION=\"Developer ID Application: Your Name (TEAMID)\"\nexport MACOS_SIGN_INSTALLER=\"Developer ID Installer: Your Name (TEAMID)\"\nexport MACOS_NOTARIZE_APPLE_ID=\"your@email.com\"\nexport MACOS_NOTARIZE_TEAM_ID=\"ABCD123456\"\nexport MACOS_NOTARIZE_PASSWORD=\"xxxx-xxxx-xxxx-xxxx\"\n\n# Build the signed and notarized package\ncd build-aux/pkg-installer\nVERSION=2.26.0 ./build-pkg.sh\n\n# Verify the signature\npkgutil --check-signature ../../build-output/Telepresence.pkg\nspctl --assess --type install ../../build-output/Telepresence.pkg\n```\n\n\n<!-- Skill/Rule: Agents Skill (.claude/skills/changelog-entry/agents/openai.yaml) -->\npolicy:\n  allow_implicit_invocation: true\n\n\n<!-- Skill/Rule: changelog-entry (.claude/skills/changelog-entry/SKILL.md) -->\n---\nname: changelog-entry\ndescription: Add a new entry to CHANGELOG.yml under the current unreleased version (or create the version block if needed), then regenerate documentation. Use when the user says things like \"add a changelog entry\", \"log this fix in the changelog\", or \"/changelog-entry\".\n---\n\n# changelog-entry\n\nAdds an entry to `CHANGELOG.yml` following the schema documented in the file header, then regenerates the derived documentation.\n\n## Inputs to gather (in order)\n\n1. **type** — one of `bugfix`, `feature`, `security`, `change`. If the user describes the change but does not pick a type, infer it:\n   - \"fixes/resolves/closes a bug\" → `bugfix`\n   - \"adds support for / introduces / new\" → `feature`\n   - \"CVE / vulnerability / hardens\" → `security`\n   - anything else affecting behavior → `change`\n2. **title** — short (≤80 chars), sentence-cased, no trailing period.\n3. **body** — 2-3 sentences. **This field is HTML, not markdown.** Use `<code>...</code>` for code, `<a href=\"...\">...</a>` for links. Prefer YAML's `>-` folded scalar so line wrapping doesn't leak literal newlines.\n4. **docs** *(optional)* — path to a docs page under `docs/` if the entry deserves a \"Learn more\" link.\n5. **image** *(optional)* — path under the `release-notes` directory if there's a visual.\n\n## Steps\n\n1. Read the top of `CHANGELOG.yml`. The first `items:` entry is the current/upcoming version.\n2. If it has `date: (TBD)`, append the new entry to its `notes:` array.\n3. If the top item is already dated (a shipped release), insert a NEW `- version: <next>` block above it with `date: (TBD)` and the single new note. Ask the user for the next version number — do not invent it.\n4. Match existing indentation exactly (2 spaces). YAML is whitespace-sensitive.\n5. After saving, run `make docs-files` to regenerate `docs/release-notes.md`, `docs/release-notes.mdx`, and `docs/variables.yml`. (The PostToolUse hook in `.claude/settings.json` will also try to do this; running it explicitly here makes the success/failure visible.)\n6. Show the user the diff: `git diff CHANGELOG.yml docs/release-notes.md docs/release-notes.mdx docs/variables.yml`.\n\n## Schema reference (from CHANGELOG.yml header)\n\n```yaml\nitems:\n  - version: 2.28.0\n    date: (TBD)              # or YYYY-MM-DD\n    notes:\n      - type: bugfix         # bugfix | feature | security | change\n        title: Short title\n        body: >-\n          Two or three sentences describing the change and why it\n          is noteworthy.  This is HTML.\n        docs: optional/path\n        image: optional/path\n```\n\n## Things to avoid\n\n- Do not edit `docs/release-notes.md`, `docs/release-notes.mdx`, or `docs/variables.yml` directly — they are generated.\n- Do not include markdown syntax in `body`; it is rendered as HTML.\n- Do not set `date:` to a concrete date for upcoming versions; `make prepare-release` does that automatically for GA versions.\n\n\n<!-- Skill/Rule: Agents Skill (.claude/skills/prepare-release/agents/openai.yaml) -->\npolicy:\n  allow_implicit_invocation: false\n\n\n<!-- Skill/Rule: prepare-release (.claude/skills/prepare-release/SKILL.md) -->\n---\nname: prepare-release\ndescription: Create the local release commit and tags by setting TELEPRESENCE_VERSION and running make prepare-release. Stops at the local commit+tags - pushing is the ship-release skill's job. Use when the user explicitly asks to prepare a release, RC, or test build. User-only.\ndisable-model-invocation: true\n---\n\n# prepare-release\n\nWraps the `make prepare-release` step so the local-tag-creation portion of a release is one explicit user action, not a chain of remembered commands. Stops at local tags; the `ship-release` skill takes over from there.\n\nThis is **user-only** by design (`disable-model-invocation: true`). The tags this skill creates will eventually drive a public release, so creating them must be an explicit user decision — never a side-effect of Claude inferring intent.\n\n## Confirm before doing anything\n\nAsk the user explicitly:\n\n1. **Version string** (`TELEPRESENCE_VERSION`) — must be one of:\n   - `vX.Y.Z-test.N` — pre-release, no Homebrew, no \"latest\"\n   - `vX.Y.Z-rc.N` — pre-release, no Homebrew, no \"latest\"\n   - `vX.Y.Z` — GA, marked latest, triggers Homebrew update\n2. **Branch** — should be a release branch (typically `release/v2`). Refuse to proceed from `main`-style branches.\n3. **Working tree** — must be clean. Run `git status`; if there are uncommitted or untracked files relevant to the build, stop.\n4. **CHANGELOG.yml status** — the top entry should have version matching `TELEPRESENCE_VERSION` (without the leading `v`). If it's still `date: (TBD)`, that's expected: `make prepare-release` sets the date for GA versions.\n\nShow all four checks to the user before running anything. Wait for explicit \"go\".\n\n## Steps\n\n```bash\nexport TELEPRESENCE_VERSION=vX.Y.Z[-suffix.N]\nmake prepare-release\n```\n\nThis creates:\n- An annotated tag `vX.Y.Z[-suffix.N]`\n- An annotated tag `rpc/vX.Y.Z[-suffix.N]`\n- A commit that bumps go.mod references inside the repo\n\nVerify with:\n\n```bash\ngit log -1 --stat\ngit tag --points-at HEAD\n```\n\n## Next: hand off to `ship-release`\n\nThis skill stops here, with the local commit and the two annotated tags. **Do not push anything.** To carry the release through CI, the docs PR, the Releases workflow, and the PR merges, invoke the `ship-release` skill (after pushing the branch and opening a PR on it — that's a manual handoff step the user does between the two skills).\n\n## Refuse to\n\n- Push anything (branch, commit, or tags). That's `ship-release`'s job.\n- Skip `make prepare-release` and just tag manually. The make target updates go.mod references; manual tagging skips that and ships a broken module.\n- Re-run `make prepare-release` on top of a previous attempt without first cleaning up the leftover tags. If `git tag --points-at HEAD` already lists the target tag, stop and report — the user has to decide whether to delete it.\n\n\n<!-- Skill/Rule: Agents Skill (.claude/skills/regression-tests/agents/openai.yaml) -->\npolicy:\n  allow_implicit_invocation: true\n\n\n<!-- Skill/Rule: regression-tests (.claude/skills/regression-tests/SKILL.md) -->\n---\nname: regression-tests\ndescription: Run, scope, or debug telepresence regression tests under regression_test/ — the integration-level suite. Use when the user wants to run an area, suite, or single test, debug a failure, or says \"/regression-tests\". Runs `go test ./regression_test` scoped with -run, in the background, writing to a log file so heavy output stays out of context.\n---\n\n# regression-tests\n\nRuns the telepresence regression suite from the main conversation, where this\nharness's shell-env quirks are known.\n\n## Background to assume\n\n- Tests live under `regression_test/` and need a working k8s cluster (kind /\n  minikube / Docker Desktop) plus images it can reach. For a local cluster set\n  `RTEST_REGISTRY=local` and LOAD the images into it rather than pushing.\n- `regression_test/README.md` is the reference: fixture-engine rules, the\n  RTEST_* table, catalogs, labels, coverage. Read it before debugging a\n  fixture problem.\n- **The shell environment always wins; there is no config file.** That means a\n  stale `export` in the user's shell silently changes a run.\n\n## Scoping: plain `go test -run`\n\nAreas are ordinary Go tests, suites and methods are subtests, so one `-run`\nexpression selects at any depth:\n\n```\ngo test ./regression_test -run '^TestIntercept$'\ngo test ./regression_test -run '^TestIntercept$/^HeaderFilter$'\ngo test ./regression_test -run '^TestIntercept$/^HeaderFilter$/^Test_PathPrefix$'\n```\n\nThere is no `TEST_SUITE`/`TEST_NAME` indirection and no make-argument dance:\ninline `VAR=value` prefixes work in this harness, so run `go test` directly\nand keep `make check-regression` for the full unscoped suite.\n\nAlways pass `-count=1` (results must never come from the test cache) and a\n`-timeout` that fits the scope: minutes for one suite, `-timeout=100m` for a\nfull run.\n\n## The run command\n\n```\nTELEPRESENCE_REGISTRY=local TELEPRESENCE_VERSION=<version> \\\n  RTEST_CONTEXT=<context> RTEST_TEARDOWN=1 \\\n  go test -count=1 -timeout=30m -run '^TestArea$/^Suite$' ./regression_test \\\n  > /tmp/rtest-suite.log 2>&1\n```\n\n- `RTEST_CONTEXT` pins the kube context. Pin it explicitly whenever the\n  machine has more than one cluster — the default is the kubeconfig's current\n  context, which is one `kubectx` away from being the wrong cluster.\n- `RTEST_TEARDOWN=1` destroys the run's resources at the end. Without it, dev\n  mode keeps namespaces, the release, and the connection for the next run to\n  adopt, which is what makes a scoped rerun take seconds.\n- `RTEST_FRESH=1` ignores adoptable resources (use when a previous run left\n  something suspect). CI implies fresh + teardown.\n- `RTEST_LABELS` / `RTEST_SKIP_LABELS` select on `compat-core`, `slow`,\n  `stress`, `flaky-retry`.\n\n## CRITICAL: a stale TELEPRESENCE_VERSION silently poisons the build\n\nThe version under test is read from the binary itself, and `make build` stamps\nit from `TELEPRESENCE_VERSION`. If the user's shell exports an old value, the\nbinary gets that version, the manager image tag no longer matches, and the run\nfails at image pull with a version that appears nowhere in your command.\n\nAlways pass `TELEPRESENCE_VERSION` explicitly to BOTH `make build` and the\n`go test` invocation, with the same value.\n\n## Rebuild before running\n\nThe suite runs the prebuilt binary plus the cluster-side images, so rebuild\nwhatever changed:\n\n- client-side Go (`pkg/`, `cmd/telepresence`) **and any `charts/` change**:\n  `make build` — the chart is go:embedded in the client binary, so a\n  chart-only edit without a rebuild silently installs the OLD chart.\n- manager / agent (`cmd/traffic`, `charts/`): `make load-images` (or\n  `make load-tel2-image`), so the cluster gets the new image.\n- `--docker` tests: `make client-image` — the daemon container runs on the\n  workstation, so it only needs to exist locally.\n\n`RTEST_REGISTRY=local` makes the manager use `pullPolicy=Never`. Reserve\n`make push-images` and a real registry for a remote cluster.\n\n## Keep heavy output out of context\n\nThis runs in the main thread, so do NOT Read or `tail` the whole log.\n\n1. Launch with `run_in_background: true`, redirecting to a fresh path.\n2. On completion, read only the summary:\n   `grep -E 'passed,|^ok|^--- FAIL|^FAIL' /tmp/rtest-suite.log | tail -5`\n   The runner's own last line is\n   `[rtest] run <stamp>: N passed, N failed, N skipped (N fixture actions)`.\n3. For a failure, the per-test artifacts are under\n   `build-output/rtest/logs/<stamp>/<TestPath>/` (cli.log, daemon logs), and\n   `manifest.json` records every test's outcome and labels.\n\n## Leftover state\n\n- `make rtest-clean` removes everything the framework created, in the cluster\n  and locally.\n- A daemon from a killed run: `telepresence quit -s`.\n- One rtest run per cluster at a time — resource names are stable by design,\n  which is what makes adoption work.\n- A leftover manager from unrelated work (\"traffic-manager in namespace X\n  already manages namespace Y\") blocks an unrestricted install: find it with\n  `kubectl get secret -A -l owner=helm`, and remember the chart also leaves\n  cluster-scoped resources (webhook config, ClusterRole/Binding) that\n  namespace deletion does not remove.\n\n## Workflow\n\n1. **Identify** the area/suite: areas are directories under\n   `regression_test/suites/`, and each suite is an `rt.Suite` type registered\n   in its `init()`.\n2. **Decide the rebuild** (see above) and run it with an explicit\n   `TELEPRESENCE_VERSION`.\n3. **Run scoped**, in the background, to a fresh log path.\n4. **Summarize:** the command, pass/fail/skip counts, failing test names, the\n   smallest excerpt explaining each failure, and the next concrete action.\n\n## Don't\n\n- Don't run the full suite unscoped without explicit user instruction — it is\n  roughly an hour serial; `make check-regression SHARD=1|2|3` runs a third of\n  it (the shard/area mapping lives in build-aux/main.mk).\n- Don't run `go test -list` or a deliberately non-matching `-run` to \"check\n  what exists\": the harness provisions real cluster resources before selection,\n  so it costs a full setup cycle. Grep the suite files instead.\n- Don't `make clobber` or destroy local images without asking.\n- Don't edit generated files: `docs/reference/cli/**`, `DEPENDENCIES.md`,\n  `DEPENDENCY_LICENSES.md`, `docs/release-notes*`.\n\n\n<!-- Skill/Rule: Agents Skill (.claude/skills/ship-release/agents/openai.yaml) -->\npolicy:\n  allow_implicit_invocation: false\n\n\n<!-- Skill/Rule: ship-release (.claude/skills/ship-release/SKILL.md) -->\n---\nname: ship-release\ndescription: Drive a Telepresence release from a prepared branch all the way through CI, docs, the Releases workflow, and PR merges. Assumes `make prepare-release` has already been run locally and the branch with that commit was pushed and a PR opened. Use when the user says \"ship the release\", or \"complete the release\". User-only.\ndisable-model-invocation: true\n---\n\n# ship-release\n\nEnd-to-end driver for releasing Telepresence. Picks up where `prepare-release` left off and carries the change through:\n\n1. Telepresence release PR (CI green + `regression` green)\n2. Docs PR in `../telepresence.io`\n3. Tag push, Releases workflow, merge of both PRs.\n\nThis is **user-only** (`disable-model-invocation: true`). A release is publicly visible and partially irreversible (tags push to GitHub, Homebrew updates for GA). Claude must never invoke this on its own.\n\n## Preconditions to verify before doing anything\n\nRun each check and stop with a clear message if any fails:\n\n1. **CWD is the telepresence repo.** `git rev-parse --show-toplevel` ends in `telepresence`.\n2. **`make prepare-release` has been run.** The current HEAD must carry both `vX.Y.Z` and `rpc/vX.Y.Z` annotated tags locally:\n   ```\n   git tag --points-at HEAD | sort\n   ```\n   Two entries expected. If the tag list is empty or missing the `rpc/` peer, stop — the user needs to run `make prepare-release` first.\n3. **Branch is pushed.** Capture `tp_branch=$(git branch --show-current)`, then:\n   ```\n   git ls-remote --exit-code origin \"refs/heads/$tp_branch\"\n   ```\n   If this fails, stop and tell the user to push the branch first.\n4. **PR exists.** `gh pr view \"$tp_branch\" --json number,state,url,headRefName`. If no PR, stop.\n5. **Sibling docs repo present.** `test -d ../telepresence.io && test -d ../telepresence.io/.git`. If not, stop.\n\nCapture once and reuse throughout:\n\n- `tp_branch` — name of the prepare-release branch.\n- `tp_version` — pick the non-`rpc/` tag from `git tag --points-at HEAD` (e.g. `v2.28.0`).\n- `docs_version` — `echo \"$tp_version\" | sed -E 's/^v([0-9]+\\.[0-9]+).*/\\1/'` (e.g. `2.28`).\n- `pr_number` — from `gh pr view`.\n\n## Phase 1 — Drive the Telepresence release PR\n\n### 1.1 Verify branch and PR (already done in preconditions)\n\n### 1.2 Wait for all checks except `regression` to be green\n\nUse:\n\n```\ngh pr checks \"$tp_branch\" --json name,state,conclusion\n```\n\nFilter out the rows whose name is exactly `regression` or `node_agent_docker_runtime` (neither has been triggered yet — the label triggers them). Match the name exactly: `regression_compat` is a different job, and it only runs behind the `compatibility test` label. For every remaining row:\n\n- `state == \"COMPLETED\"` and `conclusion == \"SUCCESS\"` → green\n- `conclusion ∈ {\"FAILURE\",\"CANCELLED\",\"TIMED_OUT\",\"ACTION_REQUIRED\"}` → **stop**. Report the failing check name and a short excerpt from `gh run view <run-id> --log-failed`. Do not advance.\n- Anything else (`IN_PROGRESS`, `QUEUED`, `PENDING`) → keep waiting.\n\n**Polling cadence:** these checks (lint, unit tests, license, image-scan) typically finish in 5-15 min. Use `ScheduleWakeup` with `delaySeconds=180` while any check is still running. Do not tight-loop with sleeps.\n\n### 1.3 Wait for `regression` to be green\n\n`regression` starts automatically with the push (the release branch lives in\nthis repository); there is nothing to trigger. If it needs another attempt,\nuse \"Re-run all jobs\" on its workflow run (`gh run rerun <run-id>`).\n\nThe regression suite runs as three parallel shards (~30 min including cluster setup), summed into the single `regression` context; the node-agent job runs beside them. Use `ScheduleWakeup` with `delaySeconds` around **900**. Poll with the same `gh pr checks` query, looking at the `regression` and `node_agent_docker_runtime` rows.\n\n- Success → continue to Phase 2.\n- Failure / cancellation → **stop and report**. Pull failed-step logs with `gh run view <run-id> --log-failed`.\n- Still running after ~90 minutes → tell the user and stop (workflow may be stuck).\n\n## Phase 2 — Create the docs PR\n\nDone in the sibling repo `../telepresence.io`. Each shell step below is a separate Bash call (no `&&` chains), and `cd` to switch repos.\n\n### 2.1 Pull master\n\n```\ncd ../telepresence.io\ngit checkout master\ngit pull\n```\n\n### 2.2 Create branch with the same name as the telepresence PR branch\n\n```\ngit checkout -b \"$tp_branch\"\n```\n\nIf that branch already exists locally from a previous attempt, stop and ask whether to reuse, reset, or rename.\n\n### 2.3 + 2.4 Export variables\n\n```\nexport DOCS_VERSION=\"$docs_version\"   # e.g. 2.28 — note: no patch number\nexport DOCS_BRANCH=\"$tp_branch\"\n```\n\n(Per CLAUDE.md, `export` in its own Bash call, then use in subsequent calls. Shell state persists between calls in a session.)\n\n### 2.5 Generate\n\n```\nmake generate-version\n```\n\n### 2.6 Verify output\n\n```\nls versioned_docs/version-\"$DOCS_VERSION\"\ngit status\n```\n\nExpectations:\n\n- **Minor release** (first time this `2.X` is generated): the directory `versioned_docs/version-$DOCS_VERSION/` appears as untracked.\n- **Bugfix release** (directory already existed): files within it are modified.\n\nIf `versioned_docs/version-$DOCS_VERSION` is absent or `git status` shows no changes, **stop and report** — `make generate-version` did not do anything useful.\n\n### 2.7 Build the site locally before pushing\n\nNetlify (the `deploy/netlify`, `Pages changed`, `Header rules`, `Redirect rules`\nchecks) and the `Check`/`Lint` GitHub jobs all run `yarn build` (docusaurus\nbuild). Run it locally first so a broken build is caught here, not after a\npush-and-wait CI cycle:\n\n```\n# If node_modules is absent: yarn install --frozen-lockfile\nyarn build\n```\n\n- Exit 0 → the production build (including the new `version-$DOCS_VERSION`)\n  compiled. Proceed to the PR.\n- Non-zero → **stop and do not push.** Read the error; it names the offending\n  file and line.\n\n**Most common failure: MDX parse error in `release-notes.mdx`.** A `.mdx` file\nis JSX, so literal `{` / `}` **inside an HTML element** like\n`<code>{cmd, stdout}</code>` are parsed as JS expressions and fail with\n`Could not parse expression with acorn`. (Braces inside Markdown backtick\nspans — `` `{tcp|udp}` `` — are safe.) These release-notes files are generated,\nso fix the **source**, not the generated copy:\n\n1. In the telepresence repo, edit the offending `CHANGELOG.yml` entry to remove\n   the literal braces (rephrase, e.g. `<code>cmd</code>/<code>stdout</code>`, or\n   move the snippet into a backtick span), then `make docs-files`.\n2. Commit + push that fix to the release branch (it belongs in the release PR).\n3. Back in the docs repo, re-run `make generate-version` to re-pull the fixed\n   docs, then `yarn build` again before continuing.\n\n### 2.8 Create the PR\n\n```\ngit add versioned_docs/version-\"$DOCS_VERSION\" versioned_sidebars docusaurus.config.js versions.json\n# (Add only the files that actually changed — git status will tell you which of the\n#  above moved; for a fresh minor you'll likely see all of them, for a bugfix only some.)\ngit commit -s -S -m \"Generate docs for telepresence $tp_version\"\ngit push -u origin \"$tp_branch\"\ngh pr create --base master --head \"$tp_branch\" \\\n  --title \"Generate docs for telepresence $tp_version\" \\\n  --body \"Generated with \\`make generate-version\\` DOCS_VERSION=$docs_version DOCS_BRANCH=$tp_branch.\"\n```\n\nCapture `docs_pr_number` from the `gh pr create` output URL.\n\nDo not include \"Co-Authored-By\" or \"Generated with\" trailers in the commit message or the PR body (per global preferences).\n\n### 2.9 Monitor the docs PR checks\n\n```\ngh pr checks \"$tp_branch\" --json name,state,conclusion\n```\n\nSame polling rules as Phase 1.2. If anything fails, **stop and report**.\n\n## Phase 3 — Release\n\n`cd` back to the telepresence repo for step 3.1 and 3.3a.\n\n### 3.1 Push the release tags\n\n```\ngit push origin \"$tp_version\" \"rpc/$tp_version\"\n```\n\nThis triggers the **Releases** workflow (`.github/workflows/release.yaml`). The release PR is still unmerged at this point — that is intentional. Merging now would create a new commit and move the branch tip away from the tagged commit.\n\n### 3.2 Monitor the Releases workflow\n\n```\ngh run list --workflow=release.yaml --limit 1 --json databaseId,status,conclusion,url\ngh run view <id> --json jobs\n```\n\nThe workflow requires manual approval of a protected GitHub environment (`macos-signing`) containing secrets for macOS signing. Wait for this up to **24 hours**. Poll with `ScheduleWakeup` at `delaySeconds=1800` (or longer when overnight). Surface the workflow URL early so the user can chase the approver.\n\n- If the workflow completes successfully → continue to 3.3.\n- If a job other than `build-macos-pkg` fails → **stop and report**.\n- If `build-macos-pkg` itself is never approved within 24h → tell the user; per CLAUDE.md the release still ships without `.pkg` installers, and the user can decide whether to proceed to 3.3 anyway.\n\n### 3.3 Merge both PRs — **GA versions only**\n\n**For pre-release versions (`-test.N`, `-rc.N`): skip this step entirely and\nstop here.** Both PRs stay open until the GA release ships: the release\nbranch accumulates the rc and GA prepare-release commits and merges once,\nafter GA, and the docs PR must not publish the new version's docs on\ntelepresence.io before GA exists (regenerate it from the GA branch before\nmerging). The rc's GitHub pre-release and its tags are the only public\nartifacts of a pre-release ship.\n\nFor a GA version: order does not matter. Both must use a **merge commit**\n(CLAUDE.md: never squash, never rebase).\n\n```\n# telepresence PR (in telepresence repo)\ngh pr merge \"$tp_branch\" --merge\n\n# docs PR (in ../telepresence.io)\ncd ../telepresence.io\ngh pr merge \"$tp_branch\" --merge\n```\n\nVerify each merged: `gh pr view \"$tp_branch\" --json state` should report `MERGED`.\n\n## Long-wait strategy\n\n- Anything under 5 min → don't sleep; just poll once.\n- 5-30 min waits (Phase 1.2 non-regression checks) → `ScheduleWakeup` with `delaySeconds=180`.\n- 30-60 min waits (Phase 1.3 `regression`) → `ScheduleWakeup` with `delaySeconds=1200`.\n- Hours-to-overnight (Phase 3.2 macOS signing approval) → `ScheduleWakeup` with `delaySeconds=1800` or longer.\n\nEach wake-up: re-fetch state, decide green/red/still-waiting, schedule the next wake or advance.\n\n## What \"stop and report\" means\n\n- Do not advance to the next numbered step.\n- Surface: the step that failed, the check/run name(s), the run URL(s), and a short excerpt from `gh run view <id> --log-failed`.\n- Do not retry automatically. Wait for the user to direct.\n- Do not delete branches, force-push, or close PRs. The user decides what to do.\n\n## What this skill must NEVER do\n\n- Run `make prepare-release` itself — that's a separate skill and a separate decision.\n- Push tags before all required PR checks are green (Phase 1 must complete first).\n- Merge the release PR or the docs PR for a pre-release (`-test.*`/`-rc.*`) version — both stay open until GA (see 3.3).\n- Merge PRs as squash or rebase — both repos require merge commits.\n- Trigger `regression` by any means other than the push itself or a re-run of its workflow run.\n- Approve the `macos-signing` environment programmatically — that requires a human reviewer.\n- Force-push or delete the release branch.\n\n\n<!-- Skill/Rule: proto-rpc-reviewer (.claude/agents/proto-rpc-reviewer.md) -->\n---\nname: proto-rpc-reviewer\ndescription: Use when reviewing changes to any .proto file under rpc/ or to the Go bindings generated from them. Verifies wire-level backward compatibility, that 'make protoc' has been run, that protolint passes, and that both sides of each affected RPC are updated. Surfaces incompatibilities that would break older clients, older traffic-managers, or older traffic-agents talking to a new peer.\ntools: Read, Grep, Glob, Bash\n---\n\nYou are the gRPC contract reviewer for the telepresence repository.\n\n## Communication boundaries you must consider\n\nThe repo defines four RPC surfaces; a single proto edit can ripple across them:\n\n| Boundary                               | Proto package        |\n|----------------------------------------|----------------------|\n| client/userd ↔ traffic-manager         | `rpc/manager/`       |\n| client ↔ user daemon                   | `rpc/connector/`     |\n| client ↔ root daemon                   | `rpc/daemon/`        |\n| traffic-manager ↔ traffic-agent        | `rpc/agent/`         |\n| auth                                   | `rpc/authenticator/` |\n| teleroute (docker network driver)      | `rpc/teleroute/`     |\n| shared types                           | `rpc/common/`        |\n\nEach daemon ships independently: an older client may talk to a newer traffic-manager, a newer traffic-manager may inject an older traffic-agent (mismatched manifest), and a newer agent may run alongside an older sidecar in another pod. Wire compatibility is therefore mandatory, not optional.\n\n## Checks you must run\n\n1. **Wire compatibility:**\n   - Field numbers must never be reused or repurposed.\n   - Field types must not change (e.g., int32 → int64 silently corrupts).\n   - Enum values must not be renumbered; only appended.\n   - `optional` and `repeated` are part of the wire contract; do not flip.\n   - Removing a field requires `reserved` to lock the number/name.\n2. **Generated code is in sync:** Confirm `make protoc` has been run — check that .pb.go files in the same package are touched in the same change. If not, flag and recommend running it.\n3. **Lint:** Confirm `protolint` would pass against the configured rules in `.protolint.yaml` (line length 120, ENUM_FIELD_NAMES_PREFIX disabled). Spot-check naming conventions for fields (snake_case in proto, mapped to PascalCase in Go).\n4. **Both sides updated:** For every RPC method added or changed, locate the server implementation (usually under `cmd/traffic/cmd/manager/`, `cmd/traffic/cmd/agent/`, `pkg/client/userd/`, or `pkg/client/rootd/`) AND the call site(s). If only one side is touched, flag it.\n5. **Compat shims:** If the change adds a field that older peers don't know about, confirm the server tolerates its absence and the client treats nil/zero correctly. Reject any change that requires a synchronized upgrade of both sides.\n\n## Reporting format\n\nReturn a punch list, not prose. For each finding:\n\n- **Severity:** Blocker / Risk / Nit\n- **Where:** file:line\n- **Why:** one sentence\n- **Fix:** one sentence\n\nEnd with a one-line verdict: \"Safe to merge\", \"Needs follow-up\", or \"Blocked\".\n\n## What NOT to do\n\n- Do not edit any files. You are a reviewer.\n- Do not run `make protoc` yourself; report whether it appears to have been run and let the caller decide.\n- Do not chase code-style nits unrelated to the proto/RPC contract.\n\n\n</agent_rules>"}