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
25 changes: 14 additions & 11 deletions .github/workflows/exact-artifact-sbom-attestation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -313,13 +313,13 @@ jobs:
EOF
{
printf '\n## Exact signed identity\n\n'
printf -- '- Source repository: `%s`\n' "$SOURCE_REPOSITORY"
printf -- '- Source SHA: `%s`\n' "$SOURCE_SHA"
printf -- '- Signer repository: `%s`\n' "$SIGNER_REPOSITORY"
printf -- '- Signer workflow: `%s`\n' "$signer_workflow"
printf -- '- Predicate type: `%s`\n' "$PREDICATE_TYPE"
printf -- '- Wheel: `%s`\n' "$WHEEL_FILENAME"
printf -- '- Source distribution: `%s`\n' "$SDIST_FILENAME"
printf -- "- Source repository: \`%s\`\n" "$SOURCE_REPOSITORY"
printf -- "- Source SHA: \`%s\`\n" "$SOURCE_SHA"
printf -- "- Signer repository: \`%s\`\n" "$SIGNER_REPOSITORY"
printf -- "- Signer workflow: \`%s\`\n" "$signer_workflow"
printf -- "- Predicate type: \`%s\`\n" "$PREDICATE_TYPE"
printf -- "- Wheel: \`%s\`\n" "$WHEEL_FILENAME"
printf -- "- Source distribution: \`%s\`\n" "$SDIST_FILENAME"
cat <<EOF

## Online verification commands
Expand Down Expand Up @@ -361,11 +361,14 @@ jobs:
} >> offline-attestation-evidence/README.md
(
cd offline-attestation-evidence
evidence_file_list="$(mktemp "${RUNNER_TEMP}/offline-attestation-files.XXXXXX")"
LC_ALL=C find . -maxdepth 1 -type f ! -name SHA256SUMS -printf '%f\n' \
| LC_ALL=C sort \
| while IFS= read -r evidence_file; do
sha256sum "$evidence_file"
done > SHA256SUMS
| LC_ALL=C sort > "$evidence_file_list"
mapfile -t evidence_files < "$evidence_file_list"
rm -f "$evidence_file_list"
for evidence_file in "${evidence_files[@]}"; do
sha256sum "$evidence_file"
done > SHA256SUMS
)
chmod 0444 \
offline-attestation-evidence/README.md \
Expand Down
61 changes: 56 additions & 5 deletions .github/workflows/opencode-review-dispatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -900,9 +900,11 @@ jobs:
}

append_command() {
printf '$ ' >>"$summary_file"
printf '%q ' "$@" >>"$summary_file"
printf '\n' >>"$summary_file"
{
printf '$ '
printf '%q ' "$@"
printf '\n'
} >>"$summary_file"
}

emit_captured_log() {
Expand Down Expand Up @@ -1175,13 +1177,17 @@ jobs:
--command-json "$configured_command_json"
done <<<"$configured_commands_json"
else
# The child shell expands its own cwd and PYTHONPATH.
# shellcheck disable=SC2016
run_and_capture "Python coverage with missing-line report (${project_dir})" \
bash -c 'cd "$1" && PYTHONPATH="$([ -d src ] && printf src:. || printf .)" python3 -m coverage run -m pytest tests && python3 -m coverage report --show-missing' bash "$project_dir"
fi
done < <(tracked_python_projects_with_tests)

if [ "$measured_projects" -eq 0 ]; then
if has_tracked_files '*.py'; then
# The child shell resolves the checked-out source layout.
# shellcheck disable=SC2016
run_and_capture "Python coverage with missing-line report" \
bash -c 'PYTHONPATH="$([ -d src ] && printf src:. || printf .)" python3 -m coverage run -m pytest && python3 -m coverage report --show-missing'
elif python3 -I -c 'import pytest_cov' >/dev/null 2>&1; then
Expand Down Expand Up @@ -1315,6 +1321,8 @@ jobs:
while IFS= read -r project_dir; do
if [ -f "${project_dir}/tests/test_docstrings.py" ]; then
measured_projects=1
# The child shell expands its own positional cwd.
# shellcheck disable=SC2016
run_and_capture "Python docstring coverage (${project_dir})" \
bash -c 'cd "$1" && PYTHONPATH="$([ -d src ] && printf src:. || printf .)" python3 -m pytest tests/test_docstrings.py' bash "$project_dir"
fi
Expand Down Expand Up @@ -1620,20 +1628,26 @@ jobs:
if [ -n "$package_name" ] && jq -e '.workspaces // empty' package.json >/dev/null 2>&1; then
run_and_capture "Tauri frontendDist build (${package_dir})" npm run build --workspace "$package_name"
else
# The child shell expands its own positional cwd.
# shellcheck disable=SC2016
run_and_capture "Tauri frontendDist build (${package_dir})" bash -c 'cd "$1" && npm run build' bash "$package_dir"
fi
;;
pnpm)
if [ -n "$package_name" ] && [ -f pnpm-workspace.yaml ]; then
run_and_capture "Tauri frontendDist build (${package_dir})" corepack pnpm --filter "$package_name" run build
else
# The child shell expands its own positional cwd.
# shellcheck disable=SC2016
run_and_capture "Tauri frontendDist build (${package_dir})" bash -c 'cd "$1" && corepack pnpm run build' bash "$package_dir"
fi
;;
yarn)
if [ -n "$package_name" ] && jq -e '.workspaces // empty' package.json >/dev/null 2>&1; then
run_and_capture "Tauri frontendDist build (${package_dir})" yarn workspace "$package_name" build
else
# The child shell expands its own positional cwd.
# shellcheck disable=SC2016
run_and_capture "Tauri frontendDist build (${package_dir})" bash -c 'cd "$1" && yarn build' bash "$package_dir"
fi
;;
Expand Down Expand Up @@ -1750,8 +1764,14 @@ jobs:
# coverage command still runs and reports any uncovered GPU lines
# exactly as before, so Rust repositories without GPU code are
# unaffected and no gate is weakened.
if ls /usr/share/vulkan/icd.d/lvp_icd*.json >/dev/null 2>&1; then
lvp_icd="$(ls /usr/share/vulkan/icd.d/lvp_icd*.json | head -n1)"
lvp_icd=""
for candidate in /usr/share/vulkan/icd.d/lvp_icd*.json; do
if [ -f "$candidate" ]; then
lvp_icd="$candidate"
break
fi
done
if [ -n "$lvp_icd" ]; then
export VK_ICD_FILENAMES="$lvp_icd"
export VK_DRIVER_FILES="$lvp_icd"
export WGPU_BACKEND=vulkan
Expand Down Expand Up @@ -2875,12 +2895,17 @@ jobs:
language_signal="Match changed prose"
fi

# Markdown backticks are literal; the format argument is intentional.
# shellcheck disable=SC2016
printf -- '- Preferred review language: `%s`\n' "$language_signal"
printf -- '- Rule: write human-readable review prose in the preferred language; keep file paths, identifiers, logs, quoted source, error text, and protocol literals unchanged.\n'
# shellcheck disable=SC2016
printf -- '- PR title: `%s`\n' "$(printf '%s' "$title" | tr '\r\n`' ' ' | cut -c 1-240)"
if [ -n "$body" ]; then
# shellcheck disable=SC2016
printf -- '- PR body excerpt: `%s`\n' "$(printf '%s' "$body" | tr '\r\n`' ' ' | cut -c 1-360)"
else
# shellcheck disable=SC2016
printf -- '- PR body excerpt: `[empty]`\n'
fi
}
Expand Down Expand Up @@ -3160,6 +3185,8 @@ jobs:
shift

if ! git -C "$OPENCODE_SOURCE_WORKDIR" diff "$@"; then
# Markdown backticks are literal; the format arguments are intentional.
# shellcheck disable=SC2016
printf 'Unable to collect %s from `%s` to `%s`; continue review from available changed-file evidence and direct file inspection.\n' "$description" "$PR_MERGE_BASE" "$PR_HEAD_SHA"
fi
}
Expand All @@ -3170,12 +3197,14 @@ jobs:
printf -- "- Base SHA: \`%s\`\n" "$PR_BASE_SHA"
printf -- "- Head SHA: \`%s\`\n\n" "$PR_HEAD_SHA"
if ! PR_MERGE_BASE="$(git -C "$OPENCODE_SOURCE_WORKDIR" merge-base "$PR_BASE_SHA" "$PR_HEAD_SHA")"; then
# shellcheck disable=SC2016
printf 'Merge-base discovery failed for `%s` and `%s`; falling back to base SHA for bounded diff evidence.\n\n' "$PR_BASE_SHA" "$PR_HEAD_SHA"
PR_MERGE_BASE="$PR_BASE_SHA"
fi
printf -- "- Merge base SHA: \`%s\`\n\n" "$PR_MERGE_BASE"
printf '## Current-head authority order\n\n'
printf 'Treat current-head sections in this file as authoritative for this run: Other unresolved review thread evidence, Failed GitHub Check evidence, Coverage execution evidence, Changed files, and Focused changed hunks.\n'
# shellcheck disable=SC2016
printf 'All PR reviews and comments evidence is historical context only and may contain stale bot conclusions. Do not infer active failed checks, unresolved threads, or missing changed files from those comments unless current-head evidence corroborates the same claim for Head SHA `%s`.\n\n' "$PR_HEAD_SHA"
if ! git -C "$OPENCODE_SOURCE_WORKDIR" diff --name-only --find-renames "$PR_MERGE_BASE" "$PR_HEAD_SHA" |
awk 'NF > 0 && $0 !~ /^\// && $0 !~ /(^|\/)\.\.($|\/)/ { print }' >"$OPENCODE_CHANGED_FILES_FILE"; then
Expand Down Expand Up @@ -4811,6 +4840,8 @@ jobs:
"$@"
}

# jq expands its own variables inside this literal program.
# shellcheck disable=SC2016
self_check_filter='
def self_check:
(.name // "") as $n
Expand Down Expand Up @@ -5473,6 +5504,8 @@ jobs:
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
{
printf '## OpenCode %s review body\n\n' "$event"
# Markdown backticks are literal; the format argument is intentional.
# shellcheck disable=SC2016
printf -- '- Head SHA: `%s`\n' "$HEAD_SHA"
printf -- '- Workflow run: %s\n' "$RUN_ID"
printf -- '- Workflow attempt: %s\n\n' "$RUN_ATTEMPT"
Expand Down Expand Up @@ -5819,6 +5852,8 @@ jobs:
printf '## Summary\n\n'
printf '%s\n\n' "$summary"
printf '## Adversarial validation\n\n'
# Markdown fences are literal; the format argument is intentional.
# shellcheck disable=SC2016
printf '```json\n%s\n```\n\n' "$adversarial_evidence"
printf -- '- Result: REQUEST_CHANGES\n'
printf -- '- Reason: %s\n\n' "$reason"
Expand Down Expand Up @@ -6616,6 +6651,8 @@ jobs:

case "$mode" in
failed)
# jq expands its own variables inside this literal program.
# shellcheck disable=SC2016
jq_filter='
[.[].check_runs[]?]
| sort_by((.started_at // .completed_at // .created_at // ""), (.id // 0))
Expand All @@ -6632,6 +6669,8 @@ jobs:
'
;;
pending)
# jq expands its own variables inside this literal program.
# shellcheck disable=SC2016
jq_filter='
[.[].check_runs[]?]
| sort_by((.started_at // .completed_at // .created_at // ""), (.id // 0))
Expand Down Expand Up @@ -6696,6 +6735,8 @@ jobs:
local owner="${GH_REPOSITORY%%/*}"
local name="${GH_REPOSITORY#*/}"

# GraphQL variables are expanded by GitHub, not Bash.
# shellcheck disable=SC2016
timeout "$(check_lookup_api_timeout_seconds)s" gh api graphql \
-f owner="$owner" \
-f name="$name" \
Expand Down Expand Up @@ -6849,6 +6890,8 @@ jobs:
commit_check_runs_file="$(mktemp)"
filtered_rollup_file="$(mktemp)"
successful_check_names_file="$(mktemp)"
# GraphQL variables are expanded by GitHub, not Bash.
# shellcheck disable=SC2016
if ! pr_node_id="$(timeout "$(check_lookup_api_timeout_seconds)s" gh api graphql \
-f owner="$owner" \
-f name="$name" \
Expand Down Expand Up @@ -7292,6 +7335,8 @@ jobs:
head_ref="$(printf '%s\n' "$pr_json" | jq -r '.headRefName // empty')"
[ -n "$head_ref" ] || return 1
lookup_error_file="$(mktemp)"
# jq expands its own variables inside this literal program.
# shellcheck disable=SC2016
if ! GH_TOKEN="$scan_token" timeout "$(check_lookup_api_timeout_seconds)s" \
gh api -X GET "repos/${GH_REPOSITORY}/code-scanning/alerts" \
-f "ref=refs/heads/${head_ref}" \
Expand Down Expand Up @@ -7359,6 +7404,8 @@ jobs:
printf 'OpenCode could not approve from deterministic current-head evidence because GitHub Checks have failed.\n\n'
printf '## Findings\n\n'
printf '### 1. HIGH Current-head GitHub Checks - Fix failed required checks before approval\n'
# Markdown backticks are literal; the format argument is intentional.
# shellcheck disable=SC2016
printf -- '- Problem: Failed same-head checks remain for `%s`.\n' "$HEAD_SHA"
printf -- '- Root cause: The model-unavailable evidence fallback is allowed only when peer GitHub Checks are complete and clean.\n'
printf -- '- Fix: Read and fix the failed check logs below, then rerun the current-head checks.\n'
Expand Down Expand Up @@ -7415,10 +7462,14 @@ jobs:
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
{
printf '## OpenCode required check satisfied by existing same-head approval\n\n'
# Markdown backticks are literal in these format strings.
# shellcheck disable=SC2016
printf -- '- Result: `EXISTING_CURRENT_HEAD_APPROVAL`\n'
# shellcheck disable=SC2016
printf -- '- Head SHA: `%s`\n' "$HEAD_SHA"
printf -- '- Workflow run: %s\n' "$RUN_ID"
printf -- '- Workflow attempt: %s\n' "$RUN_ATTEMPT"
# shellcheck disable=SC2016
printf -- '- Model-pool outcome: `%s`\n' "${OPENCODE_MODEL_POOL_OUTCOME:-unknown}"
printf -- '- Reason: a prior real-model OpenCode APPROVED review with passed structured adversarial probes already targets this exact head, and the fallback rechecked coverage, peer checks, code-scanning alerts, and unresolved review threads before accepting it.\n'
printf -- '- Review state: unchanged; no duplicate APPROVE review was posted from model-output-unavailable evidence.\n\n'
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/pr-review-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -509,8 +509,9 @@ jobs:
if [ "${#changed_python_files[@]}" -gt 0 ]; then
python3 -m py_compile "${changed_python_files[@]}"
fi
if [ "${#changed_workflows[@]}" -gt 0 ] && command -v actionlint >/dev/null 2>&1; then
actionlint "${changed_workflows[@]}"
if [ "${#changed_workflows[@]}" -gt 0 ]; then
ruby "$GITHUB_WORKSPACE/trusted-autofix-source/scripts/ci/lint_github_workflows.rb" \
"${changed_workflows[@]}"
Comment thread
seonghobae marked this conversation as resolved.
fi

- name: Commit and push autofix
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Separate actionlint schema/expression/Pyflakes validation from file-based
ShellCheck execution so workflow shell blocks larger than 64 KiB cannot
deadlock the write-capable autofix verifier, preserve actionlint's shell and
expression semantics, and narrowly accept GitHub's native
`concurrency.queue: max` while rejecting every other queue value until
upstream actionlint schema support is released.
- Bound head-mutation authorization to the actual selected `GH_TOKEN` as well
as its declared source, failing closed when it is missing or resolves to the
workflow `github.token`; case-fold repository host comparisons so casing
Expand Down
66 changes: 66 additions & 0 deletions docs/doctoring/actionlint-modern-schema-and-shellcheck.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Actionlint modern-schema and large-shell compatibility

Decision date: **2026-08-22**

## Incident

The write-capable PR autofix worker validates every workflow it changes with
`actionlint`. Two upstream gaps can make that fail or stall even when GitHub
accepts the workflow.

1. GitHub Actions supports `queue: max` for concurrency groups, while released
actionlint 1.7.12 still reports that key as invalid. Upstream pull request
654 tracks schema support.
2. Actionlint can deadlock while sending a workflow `run` block larger than a
pipe buffer to its ShellCheck subprocess. Upstream issue 712 reproduces the
boundary at 64 KiB. The central OpenCode review workflow contains larger
trusted shell blocks, so an autofix touching it can wait indefinitely.

These are linter transport/schema gaps, not reasons to remove workflow schema
validation or shell analysis.

## Decision

Keep actionlint as the schema, expression, and Pyflakes validator, but disable
only its ShellCheck subprocess integration with `-shellcheck=`. The trusted
`lint_github_workflows.rb` boundary uses Ruby's standard-library Psych parser to
read the same YAML scalar values, reproduces actionlint 1.7.12's workflow/job/
runner/step shell precedence, expression normalization, implicit shell setup,
and narrow rule exclusions, and invokes the installed ShellCheck against unique
regular temporary files. It parses ShellCheck JSON, restores the workflow job
and step identity in every diagnostic, preserves findings as a failing status,
and fails closed on malformed output or a missing executable.

The autofix worker ignores only actionlint's exact released-schema diagnostic
for the concurrency `queue` key. Before linting, it rejects every changed
workflow whose `queue` value is not exactly `max`; therefore the compatibility
exception cannot admit an invented queue mode.

This is a temporary compatibility boundary. Remove the queue diagnostic
exception after an actionlint release containing pull request 654 is pinned.
Remove the stdin spool only after issue 712 is fixed and a greater-than-64-KiB
regression passes directly through the pinned actionlint/ShellCheck pair.

## Verification

- A greater-than-64-KiB synthetic shell program reaches the delegated
ShellCheck executable through a regular file, without content loss.
- Bash, sh, Windows/PowerShell, Python, workflow defaults, and GitHub expression
normalization retain actionlint's effective-shell behavior.
- ShellCheck findings, malformed result JSON, actionlint failures, and invalid
concurrency queue values all fail closed with actionable workflow context.
- The offline Python-only coverage sandbox records the Ruby subprocess
contracts as unavailable instead of failing with `FileNotFoundError`; the
hosted quality job, whose runner includes Ruby, executes those contracts and
the real all-workflow lint command.

## References

GitHub. (2026, May 7). *GitHub Actions concurrency groups now allow larger
queues*. https://github.blog/changelog/2026-05-07-github-actions-concurrency-groups-now-allow-larger-queues/

Murai, R. (2025). *Support queue: max in concurrency* [Pull request #654].
GitHub. https://github.com/rhysd/actionlint/pull/654

Murai, R. (2026). *Shellcheck integration deadlocks for run blocks greater than
64 KiB* [Issue #712]. GitHub. https://github.com/rhysd/actionlint/issues/712
Loading
Loading