Skip to content

Fix and modernize PR CI workflow#3550

Draft
dannyroberts wants to merge 13 commits intomasterfrom
dmr/fix-pr-workflow
Draft

Fix and modernize PR CI workflow#3550
dannyroberts wants to merge 13 commits intomasterfrom
dmr/fix-pr-workflow

Conversation

@dannyroberts
Copy link
Member

@dannyroberts dannyroberts commented Feb 18, 2026

Technical Summary

Fixes and updates to the PR CI workflow (.github/workflows/commcare-android-pr-workflow.yml):

  1. Fix unicode tildes in keystore paths — The ~ characters in the keystore restore step and RELEASE_STORE_FILE env var were actually U+02DC (SMALL TILDE) instead of ASCII tilde, so they would not resolve to the home directory at runtime.

  2. Use default pull_request trigger types — The explicit types: [opened, edited] was wrong (edited fires on title/body changes, not new commits). Removed it entirely so the trigger uses its defaults (opened, synchronize, reopened), which is the standard way to run on every PR commit.

  3. Expose SIGNING_KEY secret — The Restore Keystore step referenced $SIGNING_KEY but never mapped the secret to an environment variable.

  4. Update all GitHub Actions to latest versionsactions/checkout v3→v6, actions/setup-python v4→v6, actions/setup-java v3→v5, actions/upload-artifact v3→v6. Migrated from archived gradle/gradle-build-action v2 to gradle/actions/setup-gradle v4 with direct ./gradlew invocations (the old arguments parameter is deprecated).

  5. Fix cross-request script compatibility — Pin Python to 3.9 (github3.py 1.3.0 uses collections.Callable, removed in Python 3.10). Use python / python -m pip instead of python3 and a third-party pip action. Checkout into commcare-android subdirectory with working-directory on Gradle steps, as the cross-request script expects this layout.

  6. Restore Gradle properties from secret — Decode GRADLE_PROPERTIES_BASE64_TODO_BREAK_UP into ~/.gradle/gradle.properties so project properties like GOOGLE_CLOUD_PROJECT_NUMBER are available to the build.

  7. Pass release signing properties via -P flags — Gradle doesn't read shell env vars as project properties, so signing config (RELEASE_STORE_FILE, RELEASE_STORE_PASSWORD, etc.) is passed explicitly via -P flags. This also overrides the Jenkins-specific keystore path from gradle.properties.

  8. Switch CI runner to ubuntu-latest — Avoids 3 macOS-specific test failures.

  9. Align Gradle tasks with Jenkins — Match the Jenkins CI task list: added bundleCommcareRelease and assembleCccStagingRelease, removed assembleCommcareDebug and assembleLtsRelease which Jenkins doesn't run.

  10. Add BrowserStack instrumentation tests — Run scripts/browserstack.py after building test APKs, matching the Jenkins CI configuration. Sets the BROWSERSTACK_DEVICES to Samsung Galaxy S10-9.0 and maps ghprbPullId to the GitHub Actions PR number for the skip-integration-tests label check.

Feature Flag

N/A

Safety Assurance

Safety story

These are CI-only changes with no impact on application code. The workflow is currently disabled and not running, but I plan to re-enable it as part of testing this.

Automated test coverage

N/A — CI workflow configuration.

QA Plan

Re-enable the workflow in GitHub Actions settings and verify it triggers on a test PR.

Labels and Review

  • Do we need to enhance the manual QA test coverage ? If yes, the "QA Note" label is set correctly
  • Does the PR introduce any major changes worth communicating ? If yes, the "Release Note" label is set and a "Release Note" is specified in PR description.
  • Risk label is set correctly
  • The set of people pinged as reviewers is appropriate for the level of risk of the change

@dannyroberts
Copy link
Member Author

retest this please

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

The pull request corrects GitHub Actions workflow configuration in the commcare-android CI/CD pipeline. It removes specific pull request trigger types (opened, edited) to allow the workflow to execute on all PR events, and fixes typographic characters in the file path specification for keystore handling by replacing incorrect tilde characters (˜) with standard tildes (~) in two locations within the release configuration step.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Suggested labels

skip-integration-tests

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title accurately summarizes the main changes—fixing CI workflow issues and modernizing dependencies—though it is somewhat generic.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required template sections with detailed technical explanations and safety assurances.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dmr/fix-pr-workflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/commcare-android-pr-workflow.yml (1)

15-15: ⚠️ Potential issue | 🔴 Critical

actions/upload-artifact@v3 is disabled and will cause workflow failures.

actions/upload-artifact@v3 was disabled on GitHub.com starting January 30, 2025—any workflow step using it will fail. The workflow also uses several other outdated actions:

  • actions/checkout@v3 → update to @v4
  • actions/setup-python@v4 → update to @v6
  • actions/setup-java@v3 → update to @v5
  • gradle/gradle-build-action@v2 → migrate to gradle/actions/setup-gradle@v3 (the original action repository was archived in February 2025)

Update all instances on the affected lines to restore CI functionality.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/commcare-android-pr-workflow.yml at line 15, Update the
GitHub Actions usages to supported versions: change actions/checkout@v3 →
actions/checkout@v4, actions/setup-python@v4 → actions/setup-python@v6,
actions/setup-java@v3 → actions/setup-java@v5, replace any
actions/upload-artifact@v3 references with actions/upload-artifact@v4, and
migrate gradle/gradle-build-action@v2 to gradle/actions/setup-gradle@v3; update
every occurrence of those action strings in the workflow so the steps (e.g., the
checkout, setup-python, setup-java, upload-artifact, and Gradle setup steps)
reference the new versions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/commcare-android-pr-workflow.yml:
- Around line 57-58: The "Restore Keystore" step is reading $SIGNING_KEY but the
secret isn’t exposed to the step; add an env mapping so the step receives the
GitHub secret (e.g., set env: SIGNING_KEY: ${{ secrets.SIGNING_KEY }}) before
running the echo | base64 -d command, ensuring the step name "Restore Keystore"
uses the SIGNING_KEY secret and the keystore file is written correctly.
- Line 67: The env value for RELEASE_STORE_FILE is using a literal "~" which
will not be shell-expanded; update the workflow so the keystore path is an
absolute, resolved path: either export the expanded path into GITHUB_ENV in the
"Restore Keystore" step (e.g. write the resolved full path into a variable and
echo it to $GITHUB_ENV) and then reference that env var as RELEASE_STORE_FILE in
the subsequent step, or write the keystore to a fixed absolute path (e.g.
/tmp/commcare-odk-keys.keystore) and set RELEASE_STORE_FILE to that same
absolute path; ensure references in the Gradle/signing steps use the same env
var name RELEASE_STORE_FILE so Gradle receives a valid absolute path.

---

Outside diff comments:
In @.github/workflows/commcare-android-pr-workflow.yml:
- Line 15: Update the GitHub Actions usages to supported versions: change
actions/checkout@v3 → actions/checkout@v4, actions/setup-python@v4 →
actions/setup-python@v6, actions/setup-java@v3 → actions/setup-java@v5, replace
any actions/upload-artifact@v3 references with actions/upload-artifact@v4, and
migrate gradle/gradle-build-action@v2 to gradle/actions/setup-gradle@v3; update
every occurrence of those action strings in the workflow so the steps (e.g., the
checkout, setup-python, setup-java, upload-artifact, and Gradle setup steps)
reference the new versions.

Comment on lines 57 to 58
- name: Restore Keystore
run: echo $SIGNING_KEY | base64 -d > ˜/.gradle/commcare-odk-keys.keystore
run: echo $SIGNING_KEY | base64 -d > ~/.gradle/commcare-odk-keys.keystore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

$SIGNING_KEY is not exposed — keystore will be written empty.

GitHub Actions secrets are not automatically available as shell environment variables; they must be explicitly passed to the step via an env: block. The "Restore Keystore" step has no such mapping, so $SIGNING_KEY resolves to an empty string and the keystore file will be empty (or contain garbage from decoding an empty string). The subsequent release build will either fail outright or silently sign with an invalid keystore.

🔑 Proposed fix: map the secret to the step env
 - name: Restore Keystore
   run: echo $SIGNING_KEY | base64 -d > ~/.gradle/commcare-odk-keys.keystore
+  env:
+    SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/commcare-android-pr-workflow.yml around lines 57 - 58, The
"Restore Keystore" step is reading $SIGNING_KEY but the secret isn’t exposed to
the step; add an env mapping so the step receives the GitHub secret (e.g., set
env: SIGNING_KEY: ${{ secrets.SIGNING_KEY }}) before running the echo | base64
-d command, ensuring the step name "Restore Keystore" uses the SIGNING_KEY
secret and the keystore file is written correctly.

RELEASE_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
RELEASE_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
RELEASE_STORE_FILE: "˜/.gradle/commcare-odk-keys.keystore"
RELEASE_STORE_FILE: "~/.gradle/commcare-odk-keys.keystore"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

~ in an env: value is NOT shell-expanded — Gradle will receive a literal ~ path.

env: values are slurped up when the workflow YAML is parsed and never interpreted through a shell that would enable variable expansion. So RELEASE_STORE_FILE will be the literal string ~/.gradle/commcare-odk-keys.keystore when passed to Gradle. Since Gradle's file() function does not expand ~, this path will not resolve to the home directory and the keystore written by the previous step (to the shell-expanded path, e.g. /Users/runner/.gradle/…) will not be found.

The recommended fix is to export the resolved path via $GITHUB_ENV in the Restore Keystore step, then reference it in the following step:

🛠️ Proposed fix using $GITHUB_ENV
 - name: Restore Keystore
-  run: echo $SIGNING_KEY | base64 -d > ~/.gradle/commcare-odk-keys.keystore
+  run: |
+    echo $SIGNING_KEY | base64 -d > ~/.gradle/commcare-odk-keys.keystore
+    echo "RELEASE_STORE_FILE=$HOME/.gradle/commcare-odk-keys.keystore" >> $GITHUB_ENV
   env:
     SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
 - name: Assemble Release apk
   uses: gradle/gradle-build-action@v2
   with:
     arguments: assembleCommcareRelease
   env:
     RELEASE_KEY_ALIAS: ${{ secrets.ALIAS }}
     RELEASE_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
     RELEASE_STORE_PASSWORD: ${{ secrets.KEY_STORE_PASSWORD }}
-    RELEASE_STORE_FILE: "~/.gradle/commcare-odk-keys.keystore"
+    RELEASE_STORE_FILE: ${{ env.RELEASE_STORE_FILE }}

Alternatively, write the keystore to an absolute path (e.g. /tmp/commcare-odk-keys.keystore) and use that same literal path in RELEASE_STORE_FILE, avoiding ~ entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/commcare-android-pr-workflow.yml at line 67, The env value
for RELEASE_STORE_FILE is using a literal "~" which will not be shell-expanded;
update the workflow so the keystore path is an absolute, resolved path: either
export the expanded path into GITHUB_ENV in the "Restore Keystore" step (e.g.
write the resolved full path into a variable and echo it to $GITHUB_ENV) and
then reference that env var as RELEASE_STORE_FILE in the subsequent step, or
write the keystore to a fixed absolute path (e.g.
/tmp/commcare-odk-keys.keystore) and set RELEASE_STORE_FILE to that same
absolute path; ensure references in the Gradle/signing steps use the same env
var name RELEASE_STORE_FILE so Gradle receives a valid absolute path.

dannyroberts and others added 5 commits February 18, 2026 18:32
The keystore paths used U+02DC (SMALL TILDE) instead of ASCII tilde,
which would fail to resolve to the home directory at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove explicit `types: [opened, edited]` — the defaults (opened,
synchronize, reopened) already cover the desired behavior and `edited`
only fires on title/body changes, not new commits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The step referenced $SIGNING_KEY but never mapped the secret to an
environment variable, so it would always produce an empty keystore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- actions/checkout v3 → v6
- actions/setup-python v4 → v6
- actions/setup-java v3 → v5
- actions/upload-artifact v3 → v6
- gradle/gradle-build-action v2 → gradle/actions/setup-gradle v4
  (gradle-build-action is archived; migrate from deprecated `arguments`
  parameter to direct ./gradlew invocations)
- py-actions/py-dependency-install v4 (already current)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pin Python to 3.9 (github3.py 1.3.0 uses `collections.Callable`,
  removed in Python 3.10)
- Use `python` / `python -m pip` instead of `python3` / third-party
  pip action to ensure setup-python managed interpreter is used
- Checkout into `commcare-android` subdirectory and add
  working-directory to Gradle steps (the cross-request script expects
  this layout to cd into repo directories)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dannyroberts dannyroberts changed the title Fix PR CI workflow trigger and keystore paths Fix and modernize PR CI workflow Feb 19, 2026
The macOS runner had 3 additional flaky test failures not seen on
Ubuntu (UpdateActivityTest, EntityListCacheIndexTest,
RecoveryMeasuresTest). Ubuntu runners are also faster and cheaper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Decode GRADLE_PROPERTIES_BASE64_TODO_BREAK_UP into
~/.gradle/gradle.properties so Gradle has access to properties like
GOOGLE_CLOUD_PROJECT_NUMBER that are needed by tests and release builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dannyroberts and others added 4 commits February 19, 2026 10:19
The gradle.properties from the secret contains a Jenkins-specific
keystore path. Override RELEASE_STORE_FILE and other signing properties
via -P flags which take highest precedence over gradle.properties.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use YAML folded scalars for multi-line Gradle commands and double-quote
all bash variable expansions to prevent globbing and word splitting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove clean, assembleCommcareDebug, and assembleLtsRelease steps.
Add bundleCommcareRelease and assembleCccStagingRelease to match the
Jenkins build. Also fix artifact name typo (commcare-arelease-pk).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Runs scripts/browserstack.py after building the test APKs, matching
the Jenkins CI configuration. Maps the Jenkins-specific ghprbPullId
env var to the GitHub Actions PR number so the skip-integration-tests
label check works.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dannyroberts and others added 2 commits February 19, 2026 17:27
Break up GRADLE_PROPERTIES_BASE64_TODO_BREAK_UP into individual
secrets referenced via env vars in a heredoc. Move signing properties
into gradle.properties so Gradle steps no longer need -P flags or
per-step env blocks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BrowserStack instrumentation tests now run in their own job that
depends on build-test-assemble, so infrastructure or flaky test
failures no longer fail the entire build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments