${{ ... }} is interpolated literally into the rendered shell script before bash parses it. Any of the following in a commit body breaks the next line:
A backtick (treated as command substitution)
An unbalanced quote
A line that, after substitution, ends mid-token
Test/coverage/badge work itself is fine — the failure is purely cosmetic at the post-test step. But it pollutes CI history and triggers cancellations on chained workflows.
Fix
Read the same information from git log instead — no shell-injection surface:
COMMIT_MSG=$(git log -1 --pretty=%B)
This was already the fallback (added in PR #31 for workflow_dispatch). Promoting it to be the only path eliminates the bug class entirely.
Verification
paths-ignore for the workflow includes documentation/** and *.md — does NOT include .gitea/** ; this PR will trigger CI on its own merge → first signal of the fix working.
No other ${{ ... }} expansions feed shell variables in the workflow (the other 3 uses are inside contains() job-level conditions which are evaluated server-side as booleans).
The git log -1 --pretty=%B path was already exercised on every merge since PR #31 (the fallback hit on workflow_dispatch but the syntax error fired before reaching it on regular pushes).
Why it was missed
PR #31 fixed the empty-message case but kept the original expression as the primary path. The fall-through was for the harmless case (empty); the dangerous path (special-char body) was untouched. Verifier-skill style review (Dim C — test reliability) would flag this: "fix removes one branch of failure but leaves the other branch + adds no regression test for the failing case."
Follow-up worth tracking: add a CI smoke that asserts the workflow renders without act --validate errors on PRs touching .gitea/workflows/.
## Symptom
Every CI run since PR #31 has failed at the same step:
```
/var/run/act/workflow/12.sh: line 34: syntax error: unexpected newline
❌ Failure - Main Update badges and version (multiple commits, single push)
```
Affected post-merge runs: #621 (PR #32), #622 (PR #33), #623 (PR #34), #624 (PR #35).
## Root cause
The "Update badges and version" step did:
```bash
COMMIT_MSG="${{ github.event.head_commit.message }}"
```
`${{ ... }}` is interpolated **literally** into the rendered shell script before bash parses it. Any of the following in a commit body breaks the next line:
- A backtick (treated as command substitution)
- An unbalanced quote
- A line that, after substitution, ends mid-token
Test/coverage/badge work itself is fine — the failure is purely cosmetic at the post-test step. But it pollutes CI history and triggers cancellations on chained workflows.
## Fix
Read the same information from `git log` instead — no shell-injection surface:
```bash
COMMIT_MSG=$(git log -1 --pretty=%B)
```
This was already the fallback (added in PR #31 for `workflow_dispatch`). Promoting it to be the only path eliminates the bug class entirely.
## Verification
- [x] `paths-ignore` for the workflow includes `documentation/**` and `*.md` — does NOT include `.gitea/**` ; this PR will trigger CI on its own merge → first signal of the fix working.
- [x] No other `${{ ... }}` expansions feed shell variables in the workflow (the other 3 uses are inside `contains()` job-level conditions which are evaluated server-side as booleans).
- [x] The `git log -1 --pretty=%B` path was already exercised on every merge since PR #31 (the fallback hit on workflow_dispatch but the syntax error fired before reaching it on regular pushes).
## Why it was missed
PR #31 fixed the empty-message case but kept the original expression as the primary path. The fall-through was for the harmless case (empty); the dangerous path (special-char body) was untouched. Verifier-skill style review (Dim C — test reliability) would flag this: "fix removes one branch of failure but leaves the other branch + adds no regression test for the failing case."
Follow-up worth tracking: add a CI smoke that asserts the workflow renders without `act --validate` errors on PRs touching `.gitea/workflows/`.
PR #31 added a fallback to git log -1 --pretty=%B for the workflow_dispatch
case (where head_commit.message is empty), but kept ${{ ... }} as the primary
source. That expression is interpolated literally into the rendered shell
script — so a commit body containing a backtick, an unbalanced quote, or even
just a newline at an unfortunate position breaks the line that follows.
Symptom: every PR since #31 has shown:
/var/run/act/workflow/12.sh: line 34: syntax error: unexpected newline
❌ Failure - Main Update badges and version (multiple commits, single push)
Fix: skip the expression entirely. git log -1 --pretty=%B reads the same
information from the actual commit and has no shell-injection surface.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Symptom
Every CI run since PR #31 has failed at the same step:
Affected post-merge runs: #621 (PR #32), #622 (PR #33), #623 (PR #34), #624 (PR #35).
Root cause
The "Update badges and version" step did:
${{ ... }}is interpolated literally into the rendered shell script before bash parses it. Any of the following in a commit body breaks the next line:Test/coverage/badge work itself is fine — the failure is purely cosmetic at the post-test step. But it pollutes CI history and triggers cancellations on chained workflows.
Fix
Read the same information from
git loginstead — no shell-injection surface:This was already the fallback (added in PR #31 for
workflow_dispatch). Promoting it to be the only path eliminates the bug class entirely.Verification
paths-ignorefor the workflow includesdocumentation/**and*.md— does NOT include.gitea/**; this PR will trigger CI on its own merge → first signal of the fix working.${{ ... }}expansions feed shell variables in the workflow (the other 3 uses are insidecontains()job-level conditions which are evaluated server-side as booleans).git log -1 --pretty=%Bpath was already exercised on every merge since PR #31 (the fallback hit on workflow_dispatch but the syntax error fired before reaching it on regular pushes).Why it was missed
PR #31 fixed the empty-message case but kept the original expression as the primary path. The fall-through was for the harmless case (empty); the dangerous path (special-char body) was untouched. Verifier-skill style review (Dim C — test reliability) would flag this: "fix removes one branch of failure but leaves the other branch + adds no regression test for the failing case."
Follow-up worth tracking: add a CI smoke that asserts the workflow renders without
act --validateerrors on PRs touching.gitea/workflows/.