← LimrunCONTENT HISTORY

Update to Limrun

Snapshot Sep 30, 2026 · 23:08 UTC · version 1.1.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "limrun-gradle",
  "description": "Build an Android app on a remote Gradle sandbox with `lim gradle build` instead of local Gradle or Android Studio, from any environment (Linux, Windows, macOS, VM, container). Use when the user wants to build an APK or AAB, sign a release with an upload key, prepare a Play Store publish, inspect build logs, or select sandbox tools and run shell commands, for native Android projects, React Native, and Expo. To run, tap, screenshot, or otherwise interact with the built APK on an emulator, use limrun-android-emulator. For iOS builds, use limrun-xcode or limrun-expo-development.",
  "included_files": [],
  "skill_md_contents": "---\nname: limrun-gradle\ndescription: \"Build an Android app on a remote Gradle sandbox with `lim gradle build` instead of local Gradle or Android Studio, from any environment (Linux, Windows, macOS, VM, container). Use when the user wants to build an APK or AAB, sign a release with an upload key, prepare a Play Store publish, inspect build logs, or select sandbox tools and run shell commands, for native Android projects, React Native, and Expo. To run, tap, screenshot, or otherwise interact with the built APK on an emulator, use limrun-android-emulator. For iOS builds, use limrun-xcode or limrun-expo-development.\"\nuser-invocable: true\neffort: high\n---\n\n# Remote Gradle build\n\nBuild Android projects on Limrun's remote Gradle sandboxes, from any\nenvironment (Linux, Windows, macOS, VM, container). `lim gradle build` syncs\nyour sources to a remote instance, runs the project's own Gradle wrapper\nthere, and streams the build output. Never fall back to local Gradle, a local\nAndroid SDK, or a local emulator. Your job doesn't end at a green build: get\nthe app running or the artifact delivered, and iterate until the user is\nsatisfied.\n\nFor iOS builds, use **`limrun-xcode`** instead of this skill. For the Expo\ndev-client loop (Metro, hot reload) on either platform, use\n**`limrun-expo-development`**; it comes back here for the Android Debug\nbuild.\n\n## Auth and CLI\n\nInstall if needed: `npm install --global lim`. Auth is `lim login` or\n`LIM_API_KEY` (it may be set outside the project, so don't ask for it just\nbecause it's missing from `.env` or the shell). The CLI is the source of truth:\nthe commands in this skill are verified, but if a flag errors or you need one\nnot shown here, check `--help` instead of guessing:\n\n```bash\nlim gradle --help\nlim gradle build --help\n```\n\n## Build an APK\n\nInstead of `./gradlew`, build with:\n\n```bash\nlim gradle build .\n```\n\nThis creates or reuses the remembered Gradle instance, syncs the current\ndirectory, and runs `assembleDebug` by default. Pick tasks explicitly with\n`--task` (repeatable):\n\n```bash\nlim gradle build . --task :app:assembleRelease\n```\n\nUse `--project-path` when the Gradle root is nested and auto-discovery is\nambiguous (for example a bare React Native repo where Gradle lives in\n`android/`; the server usually finds it on its own):\n\n```bash\nlim gradle build . --project-path android\n```\n\nExpo managed-workflow projects (no `android/` directory) are detected\nautomatically: the sandbox installs dependencies and runs `expo prebuild`\nbefore Gradle. Setting `--expo-app-dir` (monorepos) or `--abi` forces that\npipeline and errors when no Expo app is detected:\n\n```bash\nlim gradle build ./my-monorepo --expo-app-dir apps/mobile\n```\n\nFor iterating on an Expo app with Metro and hot reload rather than plain\nbuilds, use **`limrun-expo-development`**.\n\n## Detached builds and logs\n\nUse `--detach` to return once the build is accepted; a webhook is optional.\n`logs` reads the latest build without an exec ID, including persisted logs after\ninstance deletion; add `--follow` to wait for completion.\n\n```bash\nlim gradle build . --detach\nlim gradle logs\nlim gradle logs --follow\n```\n\n## Tool versions and shell commands\n\nAfter syncing, `lim gradle use` selects tools in the sandbox and installs missing versions.\nRun `lim gradle tools install` for synced project tool selections ([details](https://docs.limrun.com/docs/android/build-with-gradle)). Builds keep the project's `gradlew`; Android SDK/NDK/CMake use `sdkmanager`.\n\n```bash\nlim gradle tools\n# Node includes npm/npx.\nlim gradle use node@24 pnpm@10 yarn@4 bun@1 java@temurin-17 bundletool@1\nlim gradle tools install\nlim gradle run -- mise use --pin node@24.5.0\nlim gradle run --env APP_ENV=staging -- npm run generate\nlim gradle build . --env APP_ENV=staging\n```\n\n## Run it on an emulator\n\nUpload the built APK as a named asset, then install it on an Android instance:\n\n```bash\nlim gradle build . --upload myapp.apk\nlim android create --install-asset=myapp.apk\n```\n\nBuild uploads default to a 14-day TTL: each build pushes the asset's expiry\nto 14 days from that upload. Pass `--upload-ttl` with a Go duration (e.g.\n`720h`; `1d` is invalid) to change it.\n\nShare the signed stream URL from the create output with the user as a\nMarkdown link, such as `[Live emulator](<signed-stream-url>)`. For rebuild\niterations, patch the installed APK in place instead of recreating the\ninstance:\n\n```bash\nlim android sync ./path/to/app-debug.apk\n```\n\nFor everything else on the device (tapping, typing, element tree, screenshots,\nvideo, logcat over adb), use **limrun-android-emulator**.\n\n## Sign a release AAB\n\nThe default signing path needs NO credentials from the user:\n\n```bash\nlim gradle build . --sign --upload myapp.aab\n```\n\nOn first use, Limrun generates an upload keystore, escrows it as the\norganization's signing key for this app, and signs with it. Every later\n`--sign` build of the same app, from any machine or CI, uses the same key, so\nPlay Store uploads keep matching. The key is named by the Android application\nID, detected from `app.json` (Expo) or `app/build.gradle(.kts)`; pass\n`--application-id <id>` when detection fails or picks the wrong flavor.\n\n`--sign` makes `bundleRelease` the default task and the build fails before\nstarting if an explicit `--task` list contains no bundle task. A SUCCEEDED\nbuild means the AAB carries the signature (the server verifies it before\nupload), so don't re-verify the artifact unless the user asks.\n\nExpect one of these lines before the build starts and relay its meaning:\n\n- `Signing with the organization's upload key for <app> (newly generated).`:\n  first build of this app; the key now exists for the whole organization.\n- `Signing with the organization's upload key for <app> (existing).`: reusing\n  the escrowed key, as intended.\n\n## Bring your own upload key\n\nWhen the app already has a registered upload key (an existing Play listing),\nsign with the user's keystore instead:\n\n```bash\nlim gradle build . \\\n  --keystore upload.jks --keystore-password \"$KS_PASS\" \\\n  --key-alias upload --key-password \"$KEY_PASS\" \\\n  --upload myapp.aab\n```\n\nAll four flags travel together; the passwords can come from\n`LIM_KEYSTORE_PASSWORD` and `LIM_KEY_PASSWORD` instead of argv. Add\n`--save-key` to escrow the provided key so later builds can drop the flags and\nuse plain `--sign`. `--save-key` refuses to overwrite: if a DIFFERENT key is\nalready escrowed for the app it fails before any instance is created.\n\nCollect from the user:\n\n- the keystore file path (`.jks` or `.p12`); never commit it or paste its\n  bytes into files,\n- the keystore password and the key password (often the same value),\n- the key alias (`keytool -list -keystore <file>` shows it if unknown).\n\nFailure strings to recognize on the bring-your-own path:\n\n- `The organization already has a different upload key escrowed for <app>`:\n  `--save-key` conflict. Builds with `--sign` use the escrowed key; drop\n  `--save-key` to sign with the provided keystore for this build only, or ask\n  the user which key is the real upload key.\n- `Signing with your own key requires ... as well`: the BYO flag group is\n  incomplete; the message lists exactly the missing flags.\n- `signing <field> contains an unsupported character`: the password or alias\n  has characters outside ISO-8859-1. Change it in place with keytool\n  (`-storepasswd`, `-keypasswd`, or `-changealias`) to a Latin-1 value. Never\n  regenerate the key itself: that changes the upload key.\n\n## Publish to Play Store\n\nWith Play credentials (a service-account JSON via\n`--playstore-service-account`, or an access token via\n`--playstore-access-token`), the build publishes the signed release AAB\ndirectly, no browser involved:\n\n```bash\nlim gradle build . --sign --upload-to-playstore --playstore-service-account sa.json --auto-version-code\n```\n\n`--auto-version-code` makes the server resolve the next free versionCode from\nGoogle Play before the build and stamp it into the workspace copy\n(`expo.android.versionCode` in app.json for Expo projects, the single literal\n`versionCode` in the conventional `app/` module build script for native\nGradle projects), so repeat publishes never collide. Without it, or on\nprojects with computed or flavor-split versionCodes (which it rejects at\nrequest time), manage the versionCode yourself as below. Without Play\ncredentials you cannot run the publish itself: it is a browser flow with a\nGoogle sign-in. Prepare the artifact, upload it as an asset, and hand off:\n\n```bash\nlim gradle build . --sign --upload <app>-v<versionCode>.aab\n```\n\nTell the user to open https://console.limrun.com and, on the **Secrets** page,\nclick **Connect Play Console** to sign in with a Google account that has\nrelease access to the app (the session lives in the browser only; nothing is\nstored). Then on the **Registry** page they click **Publish to Play Store** on\nthe uploaded AAB and enter the package name (the application ID). The app\nlisting must already exist in Play Console. Google Play requires a versionCode\nit has never seen: `--auto-version-code` handles that on publish builds;\nwithout it, bump `versionCode` in `app/build.gradle(.kts)` (Expo:\n`expo.android.versionCode` in app.json) before the build.\n\nFailure strings to recognize on the `--sign` path:\n\n- `Cannot determine the Android application ID for signing`: detection found\n  no `app.json` android.package and no `applicationId` in\n  `app/build.gradle(.kts)`; pass `--application-id <id>`.\n- `--sign produces a Play-ready signed AAB; include a bundle task`: the\n  explicit `--task` list has no bundle task; add `bundleRelease` or drop\n  `--task`.\n- `the built AAB carries no signature`: the server's post-build check found an\n  unsigned bundle; the signing config was not applied. Not a problem in the\n  user's code; retry, and report it if it persists.\n\n## Gotchas\n\n- **Build errors are your job to fix.** If a build fails, read the error\n  output, fix the code, and rebuild. Don't ask the user to fix build errors.\n- **Instance reuse is per git worktree.** Commands resolve the remembered\n  instance from the worktree of your cwd; pass `--id <gradle-instance-id>`\n  (from `lim gradle list`) to target a specific one.\n- **versionCode must increase for every Play upload.** Prefer\n  `--auto-version-code` on publish builds. A rejected publish\n  saying the version code already exists means bump, rebuild, republish. If a\n  publish RETRY reports it, the earlier attempt already succeeded; don't\n  publish again.\n- **Application ID detection reads the first uncommented `applicationId`.**\n  Flavor-specific IDs and dynamic Gradle logic are out of its scope; use\n  `--application-id` there.\n- **Keystore passwords must be non-empty and ISO-8859-1.** Empty passwords and\n  characters outside Latin-1 are rejected at request time instead of failing\n  minutes into the build.\n- **Keep synced files small and out of build dirs.** Root-level `build/`,\n  `.gradle`, `.kotlin` and any `local.properties` never sync, and `.gitignore`\n  files (including nested ones) are honored. Use `--ignore <regex>` for other\n  large local artifacts and `--include <regex>` to force-sync gitignored\n  inputs the build needs.\n"
}

SHA-256: 36bbaa29902c21e4e70a8db7cc98f8df4f455f2ce68642dbdc8fcc004681a2b1