Skip to content
Agentmart
Submit a tool

play-release

Use when publishing an Android app to Google Play (upload an AAB to internal, alpha, beta or production, staged rollout), when changing the Play listing text…

Skill by tirupati17

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/tirupati17-play-release

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Use when publishing an Android app to Google Play (upload an AAB to internal, alpha, beta or production, staged rollout), when changing the Play listing text, screenshots, feature graphic or TV banner, when creating or repricing one-time in-app products or subscription base plans and offers, when checking what a Play track serves right now, or when Play rejects an upload, a versionCode, or an edit.

Use cases: writing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/play-release

What's inside

Skill name
play-release
Description
Use when publishing an Android app to Google Play (upload an AAB to internal, alpha, beta or production, staged rollout), when changing the Play listing text, screenshots, feature graphic or TV banner, when creating or repricing one-time in-app products or subscription base plans and offers, when checking what a Play track serves right now, or when Play rejects an upload, a versionCode, or an edit.
License
MIT
Instructions
97 lines in SKILL.md · view on GitHub

Before installing

  • Instructions read files in your home folder (~/.ssh, ~/.aws…)

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Google Play: publish, listings, images, in-app products

## Credentials

`cpm creds setup play` asks for each one and says where it lives:

| Name | Where to get it |
|---|---|
| `PLAY_PACKAGE` | The app's `applicationId`, exactly. Or pass `--package`. |
| `PLAY_SERVICE_ACCOUNT_JSON` | Path to a service account key. GCP console > IAM > Service accounts > create one > Keys > Add key > JSON. `cpm creds` copies it into `~/.config/cpm/<project>/keys/`. |

Before the key works:

1. Enable the **Google Play Android Developer API** on that GCP project (APIs & Services > Library).
2. Play Console > **Users and permissions** > Invite new user > the service account's email.
3. Grant **App permissions** for this app (release to production/testing, manage store listing, manage in-app products), not only Account permissions. With account-level rights alone it reads the catalogue happily and then fails on anything that touches a release.
4. The app must already exist in Play Console, and its very first AAB must be uploaded by hand; the API cannot create an app.

Environment variables of the same names win over the store (CI). Needs
`pip install google-api-python-client google-auth` in the Python that runs `cpm`;
`--help` and every local check work without it.

## Commands

Every command that changes Play is a **dry run unless `--commit`**. The dry run
does the real work (upload, set, ask Play to validate the edit) and then deletes
the edit, so what you rehearse is what will happen. After a commit each command
reads the live state back instead of trusting the response.

```bash
cpm play status                                   # what every track serves (read only)
cpm play stage                                    # copy the built AAB/APK to artifacts/<version>/ + release.json
cpm play publish production                       # rehearse
cpm play publish production --rollout 20 --commit # staged rollout
cpm play publish internal --commit
cpm play listing [--languages en-US] [--create] [--forbid-dashes] [--commit]
cpm play images --dir store-images [--commit]                       # phone screenshots + feature graphic
cpm play images --device tv --dir play-assets/tv --size 1920x1080 [--commit]
cpm play products --spec play-products.json [--commit]
cpm play prices --spec ladder.json [--markdown table.md] [--commit]   # base plans, offers, one-time prices
```

Paths default to the git toplevel of the current directory (`--repo` to override):
version from `app/build.gradle(.kts)` (`--gradle`), bundle from
`artifacts/<version>/release.json` (`--artifacts`), notes from
`release-notes/<version>/<play-language>.txt` (`--notes`), listing copy from
`store-listing/<play-language>.json`. `cpm play <command> --help` has the rest,
including the image folder layouts and the product spec format.

## Order

1. Bump `versionCode` and `versionName` in the app's build file. Literal values only.
2. Write `release-notes/<version>/en-US.txt` (plus other languages). Users read these: no flag names, no internals, at most 500 characters each.
3. Build and verify the signed AAB (signer certificate, target SDK, whatever the store requires), then stage it: your build script writes `release.json`, or run `cpm play stage`.
4. `cpm play status`, then `cpm play publish <track>` (dry run). Read what it says it is replacing.
5. `cpm play publish <track> --commit`. For production prefer `--rollout 10`/`20` first, then publish again at 100 once vitals look clean.

## Traps

- **A bundle upload times out after sending its bytes.** Resume the same Google
  upload request: the next chunk checks the server's accepted byte range, including
  whether the final chunk succeeded. The publisher uses 4 MiB chunks, progress
  output, a 180-second request timeout and bounded transport retries. Do not bump
  the version merely because the response was lost; check the existing library's
  hash before deciding that a number is occupied by different bytes.
- **`RedirectMissingLocation` during a chunked upload.** Google's 308 means
  "Resume Incomplete", not an HTTP redirect. When supplying an authenticated
  HTTP client, construct its transport with `googleapiclient.http.build_http()`;
  it removes 308 from httplib2's redirect codes. A plain `httplib2.Http` misses
  that configuration and fails on the first incomplete chunk.
- **An old bundle ships and every log looks right.** `artifacts/<version>/` survives a failed build. `publish` rehashes the AAB against `release.json` and checks it against the build file's version; never hand-edit `release.json` to get past that. Check timestamps too, and make sure the build script actually ran (a non-executable script exits 126 and leaves last run's files looking new; call it with `bash`).
- **A staged rollout quietly becomes 100%.** Only rollout 100 is `completed`; anything less is `inProgress` with `userFraction`. Play rejects `userFraction` on a completed release.
- **"versionCode already used".** A versionCode that was ever uploaded cannot be reused, even if never released. Bump it. `publish` reuses a bundle already in Play's library only when its sha256 matches `release.json`; the same number with different bytes is refused.
- **"You must let us know whether your app uses any Foreground Service permissions."** Play refuses every release edit until App content > Foreground service permissions covers each `FOREGROUND_SERVICE_*` type in the bundle, and that form only lists types it has seen in an uploaded bundle. First release with a new type: upload the bundle in an edit with no track change and commit it, fill the form (description plus a video per type), then publish; the library reuse above means no versionCode bump.
- **"This release no longer supports N devices."** A dependency's manifest merged in permissions that imply required hardware (camera, Bluetooth, media projection). Remove unused ones with `tools:node="remove"` and declare hardware `required="false"`; diff `aapt2 dump badging` of the previous and new APK whenever a native SDK arrives.
- **Build config keys missing from a local release build.** Values read from `local.properties`/env compile to empty strings and silently switch features off in production (push, analytics). Make the release build refuse to run without each one.
- **Release notes rejected mid-edit.** Over 500 characters per language, or an empty file. Checked locally before any network call.
- **Listing text.** Title 30, short 80, full 4000 characters. No ranking or price words ("best", "#1", "top", "free", "new", "sale") in the title, no emoji, no all-caps words. Play ranks title > short > full description and indexes the full one: search phrase in the title, repeat core terms naturally in the full description, never a keyword list. Changes take hours to show in search.
- **Screenshots.** At most 8 per device type (more is refused, not truncated; choose the story with `--shots`). 24-bit PNG without alpha. Feature graphic 1024x500, TV banner 1280x720. Only languages that already have a listing are touched; `--create-listings` adds one from a reviewed `metadata.json`. Adding the TV or Wear form factor and sending it for review are Console steps, not API calls.
- **In-app product ids are a contract** with whatever grants the purchase (the app, a billing backend like RevenueCat). Change them there first. Consumable vs non-consumable is not a Play setting for one-time products: the app or backend consumes the purchase.
- **Regional prices.** Give a USD base; Play converts it per region. Hand-set a region (`"IN": "24 INR"`) when conversion lands on an odd number; use a region group at a lower USD price for lower-income economies. A stale `regionsVersion` is only named in the error; `products` retries once with the version Play asks for.
- **Subscription prices (`prices`).** Per region: a hand-set price, else the App Store price when the currency matches (`match`: the JSON from `cpm appstore prices --out`), else Play's conversion of the USD price rounded to a local ending; a converted offer price is the region's new base price times the USD ratio, so the discount holds. Changing base plan prices never moves existing subscribers (no `migratePrices`). A base plan's billing period is fixed once active: `prices` compares it with the spec's `billingPeriod` and refuses to touch a mismatch. Play has no API for its per-currency price limits; `limitsUsd` converted per region is a sanity check only.
- **Abandoned edits.** Every uncommitted edit (dry run, rejected upload, Ctrl-C) is deleted in a `finally`. If a command dies harder than that, the edit expires on its own; it does not block the next one.

- **ARCore dependency type is missing during upload.** An XR dependency can add
  `com.google.ar.core.min_apk_version` without a dependency declaration. If AR is
  optional, add application metadata `com.google.ar.core=optional`, then rebuild
  and check device filtering. Do not declare AR required for a phone panorama.
- **A read-only screenshot backup edit disappears during an upload.** Opening a
  second Play edit with the same identity invalidates the first. Serialize all
  Play edits, including backups, validation runs, images and publishing.
- **Image upload exits with 503 after the commit.** The write may already have
  succeeded and only the post-commit read failed. Inspect the failing call before
  retrying the upload. Open one read-only edit, compare screenshot order and each
  returned `sha256` with the local files, then discard that edit.

From tirupati17/cpm, MIT.

More from tirupati17/cpm

Signals

Stars
0
Forks
0
Open issues
0
Last commit (this folder)
Oct 11, 2026 (18 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from tirupati17

Cite this page

play-release skill. Agent Mart. https://agentmart.dev/skills/tirupati17-play-release (last updated 2026-10-24)