CI: on a chat_web_app pull request, act runner opens a matching pull request in chat_android #137

Open
opened 2026-10-25 06:00:20 -07:00 by westfarn · 0 comments
Owner

Summary

When a pull request is opened (or updated) on chat_web_app, the self-hosted act runner must open a matching pull request on chat_android.

The Android PR is a tracking draft, not an automatic React → Kotlin port. It carries the web PR's intent, link, and changed paths so native work can land on its own branch. chat_android is Kotlin + Compose; llm-fe is the product source of truth (#111, chat_android#1).

Today nothing cross-posts. unit-tests.yml already runs on pull_request → master on runs-on: self-hosted. chat_android CI is separate and does not watch this repo.


Behavior

Trigger

New workflow .gitea/workflows/mirror-android-pr.yml in chat_web_app.

  • On: pull_request types opened, synchronize, reopened, closed
  • Branches: master
  • Runner: self-hosted (same act runner as Unit Tests / Deploy Beta / Deploy Prod)

Create

On opened / reopened, if no mirror exists yet:

  1. Read the source PR from the Actions event (title, body, number, html_url, user, head SHA, base branch, action).
  2. List changed files (git diff --name-only against the PR base, or the Gitea pulls/files API).
  3. Clone ai_ml_operations/chat_android default branch (master).
  4. Push branch mirror/web-<number> with one commit that adds docs/web-mirrors/<number>.md (Gitea will not open a PR with an empty diff). Commit message: Track chat_web_app#<number>.
  5. Open a draft pull request on chat_android:
    • Head: mirror/web-<number>
    • Base: chat_android default branch
    • Title: [web#<number>] <web PR title>
    • Body: see template below

Update

On synchronize (and on opened if the mirror branch already exists):

  • Find the existing Android PR by branch name mirror/web-<number> (fallback: search open PR bodies for the marker <!-- web-pr:<number> -->).
  • Refresh docs/web-mirrors/<number>.md and the PR body (new SHA, new file list, new web title/body).
  • Do not open a second PR.
  • Do not rewrite commits that a human added on top of the tracking commit. Only update the mirror doc (new commit on the same branch) and the PR description.

Close

On source PR closed:

  • Merged: comment on the Android PR with the merge SHA and web PR URL. Leave the Android PR open — native work may still be in progress.
  • Closed without merge: close the Android draft only if the branch contains no commits beyond the mirror-doc commits. If a person has pushed implementation commits, comment and leave it open.

Opt out

Skip create/update when the web PR body contains a line android: skip (case-insensitive). Still run the close path so an existing mirror is closed or commented. No repo labels exist today; do not depend on labels.

Loop guard

This workflow runs only in chat_web_app. Do not add a reverse mirror from chat_android back here.


Android PR body template

<!-- web-pr:<number> -->

Draft mirror of a web pull request. Implement the native equivalent on this branch. Do not expect a mechanical port — `chat_android` is Kotlin/Compose; web is `llm-fe`.

- **Web PR:** <html_url>
- **Author:** <user>
- **Head SHA:** <sha>
- **State:** <open|merged|closed>

## Web description

<web body>

## Files changed on web

<bullet list of paths>

## Native surfaces to check

Map touched `llm-fe` routes/screens to `chat_android` using `docs/SCREEN_INVENTORY.md` (Sign in, Sign up, Password reset, Dashboard/chat, Account, Documents, Analytics, Feedback). Write "none obvious" when the diff is docs, CI, or Capacitor-only (`llm-fe/android/`).

## Done when

- [ ] Native behavior matches the web PR's user-visible change, or this draft is closed with a comment explaining why Android is unaffected

The same content goes in docs/web-mirrors/<number>.md so the branch has a real diff and the link survives even if the PR body is edited.


Auth

github.token / the default Actions token is scoped to chat_web_app and cannot push or open PRs on chat_android.

Add a Gitea Actions secret on chat_web_app:

Secret Purpose
CHAT_ANDROID_PR_TOKEN Token that can push branches and open/update/close pull requests on ai_ml_operations/chat_android

Use that token for the clone, push, and API calls (https://git.aimloperations.com/api/v1/repos/ai_ml_operations/chat_android/...). Do not reuse the release token described in #121 unless it already has write on chat_android — prefer this dedicated secret.

Token must not be printed in logs.


Out of scope

  • Generating Kotlin from the web diff (no agent, no code translation in this workflow)
  • Mirroring chat_ios
  • Auto-merge of the Android PR
  • Push-to-master events that are not pull requests
  • Changing Unit Tests, Deploy Beta, or Deploy Prod
  • Capacitor llm-fe/android/ builds (#57, #121)

Acceptance criteria

  • Opening a PR against chat_web_app master makes the act runner open one draft PR on chat_android titled [web#<number>] … from branch mirror/web-<number>
  • Android PR body contains the web link, description, changed paths, and the <!-- web-pr:<number> --> marker
  • A second push to the same web PR updates that Android PR and does not create another one
  • Web PR body line android: skip prevents a new mirror
  • Merging the web PR comments on the Android PR and leaves it open
  • Closing the web PR without merging closes the Android draft only when nobody has added commits beyond the mirror doc
  • Workflow uses CHAT_ANDROID_PR_TOKEN and runs on self-hosted
  • README CI table (or ANDROID.md) mentions the mirror workflow in one short paragraph

References

  • Runner pattern: .gitea/workflows/unit-tests.yml (pull_request, self-hosted)
  • Native epic: #111
  • Android app epic: chat_android#1
  • Screen map: chat_android/docs/SCREEN_INVENTORY.md
## Summary When a pull request is opened (or updated) on `chat_web_app`, the self-hosted **act runner** must open a matching pull request on [`chat_android`](https://git.aimloperations.com/ai_ml_operations/chat_android). The Android PR is a **tracking draft**, not an automatic React → Kotlin port. It carries the web PR's intent, link, and changed paths so native work can land on its own branch. `chat_android` is Kotlin + Compose; `llm-fe` is the product source of truth ([#111](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/111), [chat_android#1](https://git.aimloperations.com/ai_ml_operations/chat_android/issues/1)). Today nothing cross-posts. `unit-tests.yml` already runs on `pull_request` → `master` on `runs-on: self-hosted`. `chat_android` CI is separate and does not watch this repo. --- ## Behavior ### Trigger New workflow `.gitea/workflows/mirror-android-pr.yml` in `chat_web_app`. - **On:** `pull_request` types `opened`, `synchronize`, `reopened`, `closed` - **Branches:** `master` - **Runner:** `self-hosted` (same act runner as Unit Tests / Deploy Beta / Deploy Prod) ### Create On `opened` / `reopened`, if no mirror exists yet: 1. Read the source PR from the Actions event (`title`, `body`, `number`, `html_url`, `user`, head SHA, base branch, action). 2. List changed files (`git diff --name-only` against the PR base, or the Gitea pulls/files API). 3. Clone `ai_ml_operations/chat_android` default branch (`master`). 4. Push branch `mirror/web-<number>` with **one commit** that adds `docs/web-mirrors/<number>.md` (Gitea will not open a PR with an empty diff). Commit message: `Track chat_web_app#<number>`. 5. Open a **draft** pull request on `chat_android`: - **Head:** `mirror/web-<number>` - **Base:** `chat_android` default branch - **Title:** `[web#<number>] <web PR title>` - **Body:** see template below ### Update On `synchronize` (and on `opened` if the mirror branch already exists): - Find the existing Android PR by branch name `mirror/web-<number>` (fallback: search open PR bodies for the marker `<!-- web-pr:<number> -->`). - Refresh `docs/web-mirrors/<number>.md` and the PR body (new SHA, new file list, new web title/body). - Do **not** open a second PR. - Do **not** rewrite commits that a human added on top of the tracking commit. Only update the mirror doc (new commit on the same branch) and the PR description. ### Close On source PR `closed`: - **Merged:** comment on the Android PR with the merge SHA and web PR URL. Leave the Android PR **open** — native work may still be in progress. - **Closed without merge:** close the Android draft **only if** the branch contains no commits beyond the mirror-doc commits. If a person has pushed implementation commits, comment and leave it open. ### Opt out Skip create/update when the web PR body contains a line `android: skip` (case-insensitive). Still run the close path so an existing mirror is closed or commented. No repo labels exist today; do not depend on labels. ### Loop guard This workflow runs only in `chat_web_app`. Do not add a reverse mirror from `chat_android` back here. --- ## Android PR body template ```markdown <!-- web-pr:<number> --> Draft mirror of a web pull request. Implement the native equivalent on this branch. Do not expect a mechanical port — `chat_android` is Kotlin/Compose; web is `llm-fe`. - **Web PR:** <html_url> - **Author:** <user> - **Head SHA:** <sha> - **State:** <open|merged|closed> ## Web description <web body> ## Files changed on web <bullet list of paths> ## Native surfaces to check Map touched `llm-fe` routes/screens to `chat_android` using `docs/SCREEN_INVENTORY.md` (Sign in, Sign up, Password reset, Dashboard/chat, Account, Documents, Analytics, Feedback). Write "none obvious" when the diff is docs, CI, or Capacitor-only (`llm-fe/android/`). ## Done when - [ ] Native behavior matches the web PR's user-visible change, or this draft is closed with a comment explaining why Android is unaffected ``` The same content goes in `docs/web-mirrors/<number>.md` so the branch has a real diff and the link survives even if the PR body is edited. --- ## Auth `github.token` / the default Actions token is scoped to `chat_web_app` and **cannot** push or open PRs on `chat_android`. Add a Gitea Actions secret on `chat_web_app`: | Secret | Purpose | |--------|---------| | `CHAT_ANDROID_PR_TOKEN` | Token that can push branches and open/update/close pull requests on `ai_ml_operations/chat_android` | Use that token for the clone, push, and API calls (`https://git.aimloperations.com/api/v1/repos/ai_ml_operations/chat_android/...`). Do not reuse the release token described in [#121](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/121) unless it already has write on `chat_android` — prefer this dedicated secret. Token must not be printed in logs. --- ## Out of scope - Generating Kotlin from the web diff (no agent, no code translation in this workflow) - Mirroring `chat_ios` - Auto-merge of the Android PR - Push-to-`master` events that are not pull requests - Changing Unit Tests, Deploy Beta, or Deploy Prod - Capacitor `llm-fe/android/` builds ([#57](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/57), [#121](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/121)) --- ## Acceptance criteria - [ ] Opening a PR against `chat_web_app` `master` makes the act runner open one **draft** PR on `chat_android` titled `[web#<number>] …` from branch `mirror/web-<number>` - [ ] Android PR body contains the web link, description, changed paths, and the `<!-- web-pr:<number> -->` marker - [ ] A second push to the same web PR updates that Android PR and does not create another one - [ ] Web PR body line `android: skip` prevents a new mirror - [ ] Merging the web PR comments on the Android PR and leaves it open - [ ] Closing the web PR without merging closes the Android draft only when nobody has added commits beyond the mirror doc - [ ] Workflow uses `CHAT_ANDROID_PR_TOKEN` and runs on `self-hosted` - [ ] README CI table (or `ANDROID.md`) mentions the mirror workflow in one short paragraph ## References - Runner pattern: `.gitea/workflows/unit-tests.yml` (`pull_request`, `self-hosted`) - Native epic: [#111](https://git.aimloperations.com/ai_ml_operations/chat_web_app/issues/111) - Android app epic: [chat_android#1](https://git.aimloperations.com/ai_ml_operations/chat_android/issues/1) - Screen map: `chat_android/docs/SCREEN_INVENTORY.md`
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ai_ml_operations/chat_web_app#137