Correct the rule: pull requests do not trigger CI either

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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YEsqYAMJKUJoM7UAavm169
This commit is contained in:
Claude
2026-08-06 22:18:24 +00:00
parent 9c9a40303e
commit 625a130366
+50 -34
View File
@@ -1,53 +1,69 @@
# Working on this repo with Claude # 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 GitHub creates **no workflow run** for anything this sandbox does — pushing a
sandbox pushes with a bot token, and GitHub creates no workflow run for it. The branch, opening a pull request, or merging one. All of it goes through the same
branch updates, CI stays silent, and nothing gets built, tested or published. 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 This was measured, not assumed:
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 | Action | Result |
repo owner and **do** trigger workflows. So: | --- | --- |
| 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 `workflow_dispatch` works because it creates a run directly instead of relying
fine — a feature branch has no workflow of its own. on an event. It is the only mechanism available from a session, so every build
2. **Open a pull request via the API.** `ci.yml` runs on has to be asked for by hand.
`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 ## Verify before merging, without publishing
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 `ci.yml` builds, tests and lints on any ref, but its staging and publishing
come from a raw push and are also dead. Trigger `ci.yml` manually with steps are gated on `github.ref == 'refs/heads/dev'`. Dispatching it on a
`workflow_dispatch` instead. **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 ## 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: integration:
- **`dev`** → `ci.yml` builds, tests, lints, then publishes a `v1.0.N-dev` - **`dev`** → `ci.yml` publishes a `v1.0.N-dev` **prerelease**, offered by the
**prerelease**. Offered by the in-app updater to anyone on the Dev channel. in-app updater to anyone on the Dev channel.
- **`main`** → `release.yml` publishes a **production** release. This is what - **`main`** → `release.yml` publishes a **production** release, which the
the updater installs for everyone on the Production channel. updater installs for everyone on the Production channel.
Confirm before merging to `main`. Note that `release.yml` runs `assembleDebug` Confirm before shipping to `main`. Note that `release.yml` runs `assembleDebug`
only — no tests, no lint — so a `main` merge is only as verified as the `dev` only — no tests, no lint — so a `main` release is only as verified as the `dev`
build it replays. build it replays.
## Gradle does not run in the sandbox ## Gradle does not run in the sandbox
`./gradlew` cannot resolve the Android plugin here: the proxy blocks `./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 `dl.google.com` with a 403. No local build, no local test run, no local lint. A
local lint. CI is the only verification available, which is the other reason dispatched CI run is the only way to find out whether a change compiles.
changes must reach it through a PR before landing.
## 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.