Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/actions/bot-token/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ runs:
permission-statuses: ${{ inputs.permission-statuses }}
permission-issues: ${{ inputs.permission-issues }}

# GIT_CONFIG_GLOBAL, not `git config --global`, which would outlive the job on a self-hosted runner.
# Identity through env vars: GIT_CONFIG_GLOBAL captured actions/checkout's temporary global
# include and doubled Authorization on submodule fetches (#121).
# The commit email needs the bot user's id, which is not the App id (create-github-app-token's README).
- id: who
shell: bash
Expand All @@ -78,9 +79,8 @@ runs:
set -euo pipefail
uid="$(gh api "users/${SLUG}%5Bbot%5D" --jq .id)"
name="${SLUG}[bot]" email="${uid}+${SLUG}[bot]@users.noreply.github.com"
cfg="$RUNNER_TEMP/bot-token.gitconfig"
if [ -f ~/.gitconfig ]; then cp ~/.gitconfig "$cfg"; else : >"$cfg"; fi
git config --file "$cfg" user.name "$name"
git config --file "$cfg" user.email "$email"
echo "GIT_CONFIG_GLOBAL=$cfg" >>"$GITHUB_ENV"
{
echo "GIT_AUTHOR_NAME=$name"; echo "GIT_AUTHOR_EMAIL=$email"
echo "GIT_COMMITTER_NAME=$name"; echo "GIT_COMMITTER_EMAIL=$email"
} >>"$GITHUB_ENV"
{ echo "name=$name"; echo "email=$email"; } >>"$GITHUB_OUTPUT"
39 changes: 36 additions & 3 deletions .github/workflows/bot-token-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,39 @@ jobs:
fi
grep -Eq '403|denied' push-site.log || { echo "::error::push to marola-site failed, but not with 403/denied"; tail -n 5 push-site.log; exit 1; }
echo "dry-run push to marola-site: refused ($(tail -n 1 push-site.log))"
[[ "$(git config --global user.email)" =~ ^[0-9]+\+${SLUG}\[bot\]@users\.noreply\.github\.com$ ]] || { echo "::error::git email is $(git config --global user.email)"; exit 1; }
[ "$(git config --global user.email)" = "$EMAIL" ]
[ "$(git config --global user.name)" = "${SLUG}[bot]" ]
git commit --allow-empty -qm probe
[[ "$EMAIL" =~ ^[0-9]+\+${SLUG}\[bot\]@users\.noreply\.github\.com$ ]] || { echo "::error::bot email is $EMAIL"; exit 1; }
got="$(git log -1 --format=%an/%ae/%cn/%ce)"
want="${SLUG}[bot]/${EMAIL}/${SLUG}[bot]/${EMAIL}"
[ "$got" = "$want" ] || { echo "::error::commit identity is $got, want $want"; exit 1; }

checkout-submodules:
if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- id: bot
uses: ./.github/actions/bot-token
with:
app-id: ${{ vars.PR_BOT_APP_ID }}
private-key: ${{ secrets.PR_BOT_KEY }}
repositories: marola,marola-site,marola-corpus,marola-ml,marola-app,marola-oods,marola-skills,marola-agents,marola-research
permission-contents: read
- uses: actions/checkout@v7
with:
repository: marola-dev/marola
submodules: true
token: ${{ steps.bot.outputs.token }}
path: umbrella
- name: no credential include persists in the global git config
run: |
if [ -n "${GIT_CONFIG_GLOBAL:-}" ] || git config --global --get include.path >/dev/null; then
echo "::error::a credential include persists in the global git config (#121)"; exit 1
fi
- name: every submodule fetches
run: git -C umbrella submodule foreach --recursive git fetch -q origin
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Each release moves `plugin.json`'s `version`, the flake package version and ever
together. A consumer adopts one by bumping its flake input, its workflow `@v…`/`devkit-ref` and its
marketplace `ref` at once.

## v0.11.1 — 2026-10-15

- `bot-token` sets the bot's git identity with `GIT_AUTHOR_*`/`GIT_COMMITTER_*` variables instead of
`GIT_CONFIG_GLOBAL`, which captured `actions/checkout`'s temporary global credential include and made
a later submodule fetch send `Authorization` twice (400 Duplicate header) (#121).

## v0.11.0 — 2026-10-14

- `bump-consumers` runs as `prepare` (nix, no key) and `publish` (marola-merge-bot, checked-in scripts
Expand Down
4 changes: 3 additions & 1 deletion docs/4-reference_workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,9 @@ newline-separated list of repo names; an empty list fails the step, because the
otherwise cover every repo the App is installed on, and so does a call with no `permission-*` input
set (`permission-contents`, `-pull-requests`, `-workflows`, `-checks`, `-statuses`, `-issues`).
Outputs: `token`, `app-slug`, `installation-id`, `user-name` and `user-email`. The git identity is
job-scoped, through `GIT_CONFIG_GLOBAL` rather than `git config --global`. A job that pushes to
job-scoped, set through `GIT_AUTHOR_*` and `GIT_COMMITTER_*` variables in `GITHUB_ENV`, not git
config: they outrank a later `git config user.*` in the same job, and `actions/checkout` with
`submodules: true` keeps working (#121). A job that pushes to
the repo it checked out needs `actions/checkout` with `persist-credentials: false`; otherwise
checkout's `GITHUB_TOKEN` extraheader is used for the push and no CI starts. `release.py --consumer`
moves its `@v…` pin with the other devkit pins.
Expand Down
Loading