diff --git a/CLAUDE.md b/CLAUDE.md index 7d93a8a..5365101 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,53 +1,69 @@ # Working on this repo with Claude -## Land changes through the GitHub API, never `git push` alone +## Nothing a session does triggers CI. Dispatch it explicitly. -**A `git push` from a Claude session does not trigger GitHub Actions.** The -sandbox pushes with a bot token, and GitHub creates no workflow run for it. The -branch updates, CI stays silent, and nothing gets built, tested or published. +GitHub creates **no workflow run** for anything this sandbox does — pushing a +branch, opening a pull request, or merging one. All of it goes through the same +integration, and GitHub suppresses event-triggered runs from it. The pull +request will say it was opened by the repo owner; that is only how the UI +attributes it, and it does not change the suppression. -The evidence, if it ever needs rechecking: every workflow run in this repo's -history is attributed to the repo owner. Commits *authored* by Claude trigger CI -normally — `d165e5ef`, `469c900e`, `d6e237ce` all did — because a human pushed -them. Commits pushed *by a session* (`43394b8`, `742add4`) produced no run at -all. It is the pushing credential that matters, not the commit author. +This was measured, not assumed: -Calls through the GitHub API (the `mcp__github__*` tools) are attributed to the -repo owner and **do** trigger workflows. So: +| Action | Result | +| --- | --- | +| Human pushes a Claude-authored commit (`d165e5ef`, `469c900e`) | CI runs | +| Session pushes `dev` / `main` (`43394b8`, `742add4`) | no run | +| Session opens a PR against `dev` (#1) | no run | +| Session calls `workflow_dispatch` | **runs** | -1. Commit to a feature branch and `git push` it. This triggers nothing, which is - fine — a feature branch has no workflow of its own. -2. **Open a pull request via the API.** `ci.yml` runs on - `pull_request: [main, dev]`, so this is what actually builds and tests the - change. Wait for it to pass. -3. **Merge via the API.** The resulting push to `dev` or `main` is attributed to - the owner and triggers the branch's workflow. +`workflow_dispatch` works because it creates a run directly instead of relying +on an event. It is the only mechanism available from a session, so every build +has to be asked for by hand. -Never merge by pushing `dev` or `main` directly from a session. It silently -skips every check and publishes nothing, leaving a branch whose head was never -built. +## Verify before merging, without publishing -If a PR needs re-verifying after more commits, note that `synchronize` events -come from a raw push and are also dead. Trigger `ci.yml` manually with -`workflow_dispatch` instead. +`ci.yml` builds, tests and lints on any ref, but its staging and publishing +steps are gated on `github.ref == 'refs/heads/dev'`. Dispatching it on a +**feature branch** therefore gives the full check — build, unit tests, Android +lint — and publishes nothing. + +So the working loop is: + +1. Commit and push the feature branch. +2. Dispatch `ci.yml` on that branch. Wait for green. This is the real review + gate, and it is the only verification that exists (see below). +3. Open the PR through the API and merge it there, so the change is reviewable + and the merge commit is honest about what happened. Neither step builds + anything. +4. Dispatch the target branch's workflow to actually build and publish: + `ci.yml` on `dev`, `release.yml` on `main`. + +Skipping step 4 leaves a branch whose head was never built and no release. ## Merging is a shipping action -Both long-lived branches publish on push, so a merge is a release, not just an +Both long-lived branches publish, so a merge is a release, not just an integration: -- **`dev`** → `ci.yml` builds, tests, lints, then publishes a `v1.0.N-dev` - **prerelease**. Offered by the in-app updater to anyone on the Dev channel. -- **`main`** → `release.yml` publishes a **production** release. This is what - the updater installs for everyone on the Production channel. +- **`dev`** → `ci.yml` publishes a `v1.0.N-dev` **prerelease**, offered by the + in-app updater to anyone on the Dev channel. +- **`main`** → `release.yml` publishes a **production** release, which the + updater installs for everyone on the Production channel. -Confirm before merging to `main`. Note that `release.yml` runs `assembleDebug` -only — no tests, no lint — so a `main` merge is only as verified as the `dev` +Confirm before shipping to `main`. Note that `release.yml` runs `assembleDebug` +only — no tests, no lint — so a `main` release is only as verified as the `dev` build it replays. ## Gradle does not run in the sandbox `./gradlew` cannot resolve the Android plugin here: the proxy blocks -`dl.google.com` with a 403. There is no local build, no local test run, and no -local lint. CI is the only verification available, which is the other reason -changes must reach it through a PR before landing. +`dl.google.com` with a 403. No local build, no local test run, no local lint. A +dispatched CI run is the only way to find out whether a change compiles. + +## A permanent fix, if you want one + +The suppression is a property of the sandbox's credential, so it cannot be +fixed from inside a session. Giving the environment a personal access token to +push with would make pushes and PRs behave normally for everyone. Until then, +the dispatch step above is the workaround.