From c464307d57a4a04daa394628409fd4696bd21de6 Mon Sep 17 00:00:00 2001 From: Bryan Li Date: Thu, 13 Aug 2026 10:07:52 -0700 Subject: [PATCH 1/3] ci: push personal staging refs with PAT Use the workflows-scoped staging PAT for trusted checkout credentials while retaining the default GitHub token fallback when the secret is absent. Signed-off-by: Bryan Li Co-authored-by: omnigent --- .github/workflows/personal-staging-hourly.yml | 6 ++++++ .github/workflows/personal-staging.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/personal-staging-hourly.yml b/.github/workflows/personal-staging-hourly.yml index be9da235a2..42bc3d9545 100644 --- a/.github/workflows/personal-staging-hourly.yml +++ b/.github/workflows/personal-staging-hourly.yml @@ -49,6 +49,9 @@ jobs: - name: Checkout fork main with full history uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + # App tokens cannot push workflow-file changes; prefer the + # workflows-scoped PAT, with today's token as fallback. + token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} ref: main fetch-depth: 0 @@ -160,6 +163,9 @@ jobs: - name: Checkout fork with full history uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + # App tokens cannot push workflow-file changes; prefer the + # workflows-scoped PAT, with today's token as fallback. + token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} fetch-depth: 0 - name: Compose staging from upstream main + open btli PRs + extras diff --git a/.github/workflows/personal-staging.yml b/.github/workflows/personal-staging.yml index 93f98cb661..6569842229 100644 --- a/.github/workflows/personal-staging.yml +++ b/.github/workflows/personal-staging.yml @@ -44,6 +44,9 @@ jobs: - name: Checkout fork main with full history uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + # App tokens cannot push workflow-file changes; prefer the + # workflows-scoped PAT, with today's token as fallback. + token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} ref: main fetch-depth: 0 @@ -124,6 +127,9 @@ jobs: - name: Checkout fork with full history uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + # App tokens cannot push workflow-file changes; prefer the + # workflows-scoped PAT, with today's token as fallback. + token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} fetch-depth: 0 - name: Compose staging from upstream main + open btli PRs From 1a11a7d45342c053da9de849f3ec871ac9f0376d Mon Sep 17 00:00:00 2001 From: Bryan Li Date: Thu, 13 Aug 2026 10:25:58 -0700 Subject: [PATCH 2/3] ci: harden personal staging PAT pushes Use the PAT for the floating nightly ref update, suppress automation-triggered push fan-out, and guard nightly main synchronization against non-main dispatches. Signed-off-by: Bryan Li Co-authored-by: omnigent --- .github/workflows/personal-staging-hourly.yml | 7 ++++--- .github/workflows/personal-staging.yml | 12 ++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/personal-staging-hourly.yml b/.github/workflows/personal-staging-hourly.yml index 42bc3d9545..42f8be4515 100644 --- a/.github/workflows/personal-staging-hourly.yml +++ b/.github/workflows/personal-staging-hourly.yml @@ -50,7 +50,8 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: # App tokens cannot push workflow-file changes; prefer the - # workflows-scoped PAT, with today's token as fallback. + # workflows-scoped PAT, with GitHub token as fallback. + # PAT pushes trigger workflows, so merge commits carry [skip ci]. token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} ref: main fetch-depth: 0 @@ -73,7 +74,7 @@ jobs: # (unmerged index entries) are skippable. A bad object or a transport # error must stay loud instead of hiding behind an hourly warning. merge_or_soft_fail() { - if git merge --no-edit "$1"; then + if git merge -m "Merge upstream main [skip ci]" "$1"; then return 0 fi if [ -z "$(git ls-files -u)" ]; then @@ -164,7 +165,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: # App tokens cannot push workflow-file changes; prefer the - # workflows-scoped PAT, with today's token as fallback. + # workflows-scoped PAT, with GitHub token as fallback. token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} fetch-depth: 0 diff --git a/.github/workflows/personal-staging.yml b/.github/workflows/personal-staging.yml index 6569842229..7e91248ce2 100644 --- a/.github/workflows/personal-staging.yml +++ b/.github/workflows/personal-staging.yml @@ -35,7 +35,7 @@ jobs: # HEAD directly and never reads fork main. sync-main: # Fork-only, mirroring upstream's `if: github.repository == 'omnigent-ai/omnigent'` guards. - if: github.repository == 'btli/omnigent' + if: github.repository == 'btli/omnigent' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 10 permissions: @@ -45,7 +45,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: # App tokens cannot push workflow-file changes; prefer the - # workflows-scoped PAT, with today's token as fallback. + # workflows-scoped PAT, with GitHub token as fallback. token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} ref: main fetch-depth: 0 @@ -57,7 +57,7 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git remote add upstream https://github.com/omnigent-ai/omnigent.git git fetch upstream main - if ! git merge --no-edit FETCH_HEAD; then + if ! git merge -m "Merge upstream main [skip ci]" FETCH_HEAD; then { echo "## sync-main: merge conflict with upstream main" echo "" @@ -128,7 +128,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: # App tokens cannot push workflow-file changes; prefer the - # workflows-scoped PAT, with today's token as fallback. + # workflows-scoped PAT, with GitHub token as fallback. token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} fetch-depth: 0 @@ -421,6 +421,10 @@ jobs: # commit; the release is then edited in place (no delete/recreate, so # the stable releases/download/nightly-latest/ URL never 404s). - name: Update nightly-latest + env: + # App tokens cannot update refs containing workflow-file changes; + # prefer the workflows-scoped PAT, with GitHub token as fallback. + GH_TOKEN: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} run: | set -euo pipefail gh api -X PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/nightly-latest" \ From 24731622167405ea942b389b7675a34d3535ba7f Mon Sep 17 00:00:00 2001 From: Bryan Li Date: Thu, 13 Aug 2026 13:47:11 -0700 Subject: [PATCH 3/3] ci: gate staging PAT push jobs Preserve GitHub token fallback merge behavior, force skip-marked merge commits only for PAT pushes, and restrict PAT-bearing jobs to the staging-push environment. Signed-off-by: Bryan Li Co-authored-by: omnigent --- .github/workflows/personal-staging-hourly.yml | 12 +++++++++--- .github/workflows/personal-staging.yml | 16 ++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/personal-staging-hourly.yml b/.github/workflows/personal-staging-hourly.yml index 42f8be4515..d1955cfb9d 100644 --- a/.github/workflows/personal-staging-hourly.yml +++ b/.github/workflows/personal-staging-hourly.yml @@ -42,6 +42,7 @@ jobs: # from pushing fork main with this job's write token. if: github.repository == 'btli/omnigent' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest + environment: staging-push timeout-minutes: 10 permissions: contents: write @@ -51,12 +52,14 @@ jobs: with: # App tokens cannot push workflow-file changes; prefer the # workflows-scoped PAT, with GitHub token as fallback. - # PAT pushes trigger workflows, so merge commits carry [skip ci]. + # PAT pushes trigger workflows, so PAT merges force a [skip ci] commit. token: ${{ secrets.STAGING_PUSH_TOKEN || github.token }} ref: main fetch-depth: 0 - name: Merge upstream main and push (soft-fail on conflict) + env: + HAS_PAT: ${{ secrets.STAGING_PUSH_TOKEN != '' }} run: | set -euo pipefail git config user.name "github-actions[bot]" @@ -74,8 +77,10 @@ jobs: # (unmerged index entries) are skippable. A bad object or a transport # error must stay loud instead of hiding behind an hourly warning. merge_or_soft_fail() { - if git merge -m "Merge upstream main [skip ci]" "$1"; then - return 0 + if [ "$HAS_PAT" = "true" ]; then + git merge --no-ff -m "Merge upstream main [skip ci]" "$1" && return 0 + else + git merge --no-edit "$1" && return 0 fi if [ -z "$(git ls-files -u)" ]; then echo "::error::sync-main: git merge failed with no conflicting paths — not a content conflict" @@ -157,6 +162,7 @@ jobs: # security boundary — see the nightly workflow's integrate job. if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest + environment: staging-push timeout-minutes: 20 permissions: contents: write diff --git a/.github/workflows/personal-staging.yml b/.github/workflows/personal-staging.yml index 7e91248ce2..ea55cbfd6c 100644 --- a/.github/workflows/personal-staging.yml +++ b/.github/workflows/personal-staging.yml @@ -11,7 +11,9 @@ # credentials unpersisted and may not write shared caches. The privileged # integrate job runs only the stdlib composer; the keystore only ever appears # in android-sign, which re-signs the APK unconditionally so the untrusted -# build's own signature never ships. +# build's own signature never ships. PAT-bearing jobs use the main-only +# staging-push environment so staging-tree workflows cannot receive the PAT; +# dev-tag pushes may enqueue release/image workflows, whose repo guards skip. name: Personal Staging Nightly on: @@ -37,6 +39,7 @@ jobs: # Fork-only, mirroring upstream's `if: github.repository == 'omnigent-ai/omnigent'` guards. if: github.repository == 'btli/omnigent' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest + environment: staging-push timeout-minutes: 10 permissions: contents: write @@ -51,13 +54,20 @@ jobs: fetch-depth: 0 - name: Merge upstream main and push + env: + HAS_PAT: ${{ secrets.STAGING_PUSH_TOKEN != '' }} run: | set -euo pipefail git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git remote add upstream https://github.com/omnigent-ai/omnigent.git git fetch upstream main - if ! git merge -m "Merge upstream main [skip ci]" FETCH_HEAD; then + if [ "$HAS_PAT" = "true" ]; then + merge_command=(git merge --no-ff -m "Merge upstream main [skip ci]" FETCH_HEAD) + else + merge_command=(git merge --no-edit FETCH_HEAD) + fi + if ! "${merge_command[@]}"; then { echo "## sync-main: merge conflict with upstream main" echo "" @@ -116,6 +126,7 @@ jobs: # server-enforced staging-signing Environment on android-sign. if: always() && needs.test-composer.result == 'success' && needs.sync-main.result != 'cancelled' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest + environment: staging-push timeout-minutes: 20 permissions: contents: write @@ -356,6 +367,7 @@ jobs: needs: [integrate, android-sign] if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest + environment: staging-push timeout-minutes: 15 permissions: contents: write