From 9c9a40303ee4b2dbae3a5c17eac9f2aa43c012c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 22:13:52 +0000 Subject: [PATCH 1/2] Add CLAUDE.md recording how changes must reach CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A git push from a Claude session is made with a bot token, and GitHub creates no workflow run for it — the branch moves and CI stays silent. Today that put a change on dev and then main having only ever been built after the fact, and both branches needed a manual workflow_dispatch to produce anything. Record the working rule that avoids it: push the feature branch, then open and merge the pull request through the GitHub API, whose calls are attributed to the repo owner and do trigger workflows. ci.yml already listens on pull_request for main and dev, so the PR is what builds the change before it lands. Also note what a merge actually does here — dev publishes a dev-channel prerelease, main publishes a production release — and that Gradle cannot run in the sandbox, so CI is the only verification available. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YEsqYAMJKUJoM7UAavm169 --- CLAUDE.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7d93a8a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,53 @@ +# Working on this repo with Claude + +## Land changes through the GitHub API, never `git push` alone + +**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. + +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. + +Calls through the GitHub API (the `mcp__github__*` tools) are attributed to the +repo owner and **do** trigger workflows. So: + +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. + +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. + +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. + +## Merging is a shipping action + +Both long-lived branches publish on push, 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. + +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` +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. From 625a130366151bc231353c666a99d9b305c03406 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 22:18:24 +0000 Subject: [PATCH 2/2] Correct the rule: pull requests do not trigger CI either MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opening this pull request through the API produced no workflow run, so the previous version of this file was wrong about the API route being the fix. The suppression covers everything the sandbox does — pushes, pull request opens, and merges — because it follows the integration, not the account the UI attributes the action to. workflow_dispatch is the only mechanism that works, because it creates a run directly rather than relying on an event. Record the loop that follows from that, including the part worth having: ci.yml gates only its staging and publishing steps on refs/heads/dev, so dispatching it on a feature branch runs build, tests and lint while publishing nothing. That is a real pre-merge gate with no YAML changes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YEsqYAMJKUJoM7UAavm169 --- CLAUDE.md | 84 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 34 deletions(-) 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.