tjdrhs90/rn-launch-harness/rn-harness-submit
Claude Code and agent skill by tjdrhs90/rn-launch-harness
- community
Checking that the SKILL.md file is still in the repository.
Phase 10 — Submit to App Store Connect (iOS) and Google Play (Android). iOS fully automated, Android pauses for manual steps.
Install
npx vskill@latest install tjdrhs90/rn-launch-harness/rn-harness-submitSKILL.md
rn-harness-submit — Phase 10: Store Submission
Submit the app to App Store and Google Play. Uses App Store Connect API (iOS) and Google Play Developer API v3 (Android).
Trigger
Called by the orchestrator as Phase 10.
Input
docs/harness/config.md(API keys, developer info)docs/harness/handoff/build-result.md(build URLs)docs/harness/store-assets/(screenshots + metadata).env(credentials)
Part 0: Pre-submission checks — run these before anything else
Every one of these has caused a real rejection. See docs/store-gotchas.md for the full write-ups. Four checks, all cheap:
1. No placeholder purpose strings in the generated `Info.plist`. Reading app.json is not enough — Expo applies a package's config plugin whenever the package is a dependency, whether or not it is listed in `plugins`. expo-image-picker and expo-camera inject NSMicrophoneUsageDescription = "Allow $(PRODUCT_NAME) to access your microphone", which Apple rejects under 5.1.1.
Fix — if the app only picks photos, drop the key rather than writing a string for a capability the app does not use:
2. Every declared permission belongs to a feature a user can reach. For each NS*UsageDescription and each Android permission, name the screen that uses it and the path a user takes to get there. A permission for an unreachable or not-yet-built feature is a rejection, and "we will use it later" in the review notes makes it worse.
3. Age rating declarations are complete — especially advertising = Yes for any AdMob build (step A-3.9). Cannot be verified over the API; check the Connect UI.
4. Android: read the *merged* manifest, not yours. Dependencies merge in their own permissions, and Play Console will demand a declaration for them.
FOREGROUND_SERVICE_* needs a Play Console declaration with a mandatory demo video; SCHEDULE_EXACT_ALARM, QUERY_ALL_PACKAGES, and READ_MEDIA_* need declarations too. Past the compliance deadline these block updates while the live listing stays up — easy to miss.
Part A: iOS — App Store Submission (Fully Automated)
A-1: Credential Check
Read from .env:
ASC_KEY_ID— API Key ID (10 chars)ASC_ISSUER_ID— Issuer ID (UUID)ASC_PRIVATE_KEY_PATH— .p8 file path (default:./credentials/asc-api-key.p8)
If missing or .p8 file not found → AskUserQuestion:
A-2: Binary Upload
If `config.md → build.method` is `eas`:
Or with a specific build URL:
If `build.method` is `local` (EAS-free — see `rn-harness-build-local`): upload the local .ipa without EAS, using the same ASC API key:
Android is unaffected — Part B already uploads the AAB via the Google Play API, EAS or not.
A-3: App Store Connect API — Metadata
EAS Submit handles the binary upload. Additional metadata via ASC API:
- Check/Create App Record
- Check if Bundle ID exists → if not, register via
POST /v1/bundleIds - Create app record via
POST /v1/apps
- Create App Store Version
POST /v1/appStoreVersions(version: "1.0.0", platform: IOS)
- App Info
- Primary locale:
ko(Korean) - Primary category from PRD
- Privacy policy URL:
config.md → developer.privacy_url
- Localization (ko locale)
- App name (from title.txt), description (from full_description.txt), subtitle (from subtitle.txt)
- Keywords (from keywords.txt) — MUST be 95-100 chars including commas.
- Verify
keywords.txtlength before upload - If < 95 chars: return to screenshot phase to regenerate (don't waste ASO budget)
- If > 100 chars: reject (API will fail)
- Marketing URL:
config.md → developer.homepage_url - Support URL:
config.md → developer.email(or privacy URL)
- Copyright
config.md → developer.copyright
- Screenshot Upload (idempotent — DELETE existing before upload)
- iPhone 6.7" (
APP_IPHONE_67) screenshots only (supportsTablet: false → no iPad) - CRITICAL — clear existing first, or re-runs pile up duplicates. The ASC API appends to a screenshot set; uploading again without clearing stacks a second (third…) copy of every shot. This is the #1 cause of "each screenshot appears twice." Mirror the Android path, which already deletes before upload (
templates/publish.js→images.deleteall). - Order per locale:
GETthe version'sappStoreVersionLocalizations→appScreenshotSetsforscreenshotDisplayType = APP_IPHONE_67- `DELETE` every existing `appScreenshots` in that set (or delete the set and recreate)
- Create the set if missing → reserve → upload binary → commit
- One display type only. Upload each PNG to exactly one
screenshotDisplayType. Do NOT reuse the same image across multiple size slots (6.7"/6.5"/…) — that makes "the same picture appears several times" on the listing.
- Build Assignment
- Wait for build processing (
processingState: VALID) - Link build to version
- Review Information
- Contact:
config.md → ios_review(first_name, last_name, phone) - Demo account (if needed — AskUserQuestion)
- Encryption: No (ITSAppUsesNonExemptEncryption: false)
- Notes (
appStoreReviewDetail.notes) — readable and writable, and reviewers read it.
This is the only reviewer-facing channel the API can touch (Resolution Center has none). Write what the app does that a reviewer might otherwise misread. State facts only — a note saying a permission is there "for a future feature" gets the build rejected. When resubmitting after a rejection, update the notes BEFORE submitting, not after.
- Age Rating Declarations — required, and easy to miss
PATCH /v1/apps/{id}/relationships/ageRatingDeclaration (or the version's ageRatingDeclaration). Apple added questions in 2026; an app that only answered the old questionnaire has them unset and cannot enter review:
`` userGeneratedContent · messagingAndChat · ageAssurance · gunsOrOtherWeapons advertising · healthOrWellnessTopics · parentalControls ``
- If AdMob is enabled, `advertising` MUST be Yes. Banners count. Getting this wrong
gets the build bounced by Apple's automated analysis before a human sees it.
- Unset attributes surface as
409 STATE_ERROR.ENTITY_STATE_INVALIDat submit time. - `GET` on `ageRatingDeclarations` returns `403` — write-only. You cannot verify over
the API; check the Connect web UI if you need to confirm.
- Submit for Review — three calls, not one
POST /v1/appStoreVersionSubmissions is deprecated and returns 403 "Allowed operation is: DELETE". Use reviewSubmissions:
`` a. POST /v1/reviewSubmissions { platform: IOS, app: <appId> } → reuse an existing submission in state READY_FOR_REVIEW if one exists b. POST /v1/reviewSubmissionItems { reviewSubmission: <id>, appStoreVersion: <id> } c. PATCH /v1/reviewSubmissions/<id> { submitted: true } ``
Resubmitting after a rejection — the rejected version is still attached to the submission that was rejected, so step (b) fails with STATE_ERROR.ITEM_PART_OF_ANOTHER_SUBMISSION. Detach first: for every submission in state UNRESOLVED_ISSUES, PATCH /v1/reviewSubmissionItems/<itemId> { removed: true }.
Also do not create a new version for a rejection. REJECTED, DEVELOPER_REJECTED, METADATA_REJECTED, and INVALID_BINARY are all editable — attach the new build to the same version. Creating a second version with the same version string fails with ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE.
Metadata stays editable through `WAITING_FOR_REVIEW` and `IN_REVIEW`. It locks at READY_FOR_SALE, where PATCH returns 409. A wrong release note is fixable right up until release — and unfixable one minute after.
A-4: iOS Result
Part B: Android — Google Play Submission
B-1: Manual Steps (API Limitations)
AskUserQuestion — PAUSE and guide user through mandatory manual steps:
B-2: Service Account Check
Read from .env:
GOOGLE_PLAY_SA_JSON— path to service account JSON
If missing → AskUserQuestion:
B-3: Generate publish.js Script
Create scripts/publish.js in the project based on the proven pattern:
Install dependency:
B-4: Prepare Metadata Files
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/rn-harness-submit/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/tjdrhs90/rn-launch-harness/rn-harness-submit)