Skip to content

🐛 QD-14800 Patch workflow job's check-run instead of creating an orphan - #599

Open
Anna Zhukova (bindreams) wants to merge 5 commits into
mainfrom
azhukova/QD-14800
Open

🐛 QD-14800 Patch workflow job's check-run instead of creating an orphan#599
Anna Zhukova (bindreams) wants to merge 5 commits into
mainfrom
azhukova/QD-14800

Conversation

@bindreams

Copy link
Copy Markdown

Summary

Fixes QD-14800.

On a PR in a repo with two or more workflows triggered by pull_request, qodana-action posted its summary check-run under the wrong workflow's name in the PR Checks rollup (e.g. "Semantic PR Title / Qodana for JVM" when Qodana actually ran in the CI workflow).

Root cause. checks.create cannot specify a check-suite; GitHub auto-groups every check-run posted by the github-actions app on a given SHA into the first existing check-suite for that app, which on multi-workflow repos is whichever workflow finished its first job earliest.

Fix. PATCH the workflow job's already-existing check-run instead of creating a parallel one. actions.listJobsForWorkflowRun returns jobs whose id field IS the check-run ID; patching that keeps the result inside the workflow's own check-suite. Do not pass status/conclusion (GitHub Actions owns those for job check-runs). Falls back to the legacy checks.create path if running outside Actions or if lookup/PATCH fails.

Changes

  • New helpers in scan/src/utils.ts:
    • resolveJobCheckRunId(client) — paginates listJobsForWorkflowRun, matches the running job by GITHUB_JOB exact → ${GITHUB_JOB} ( prefix (matrix) → RUNNER_NAME, with RUNNER_NAME sub-filter + status === 'in_progress' tie-break to disambiguate concurrently-running matrix legs. Memoized at module scope so chunked annotation publishing (≥50 annotations) doesn't re-list jobs each chunk.
    • updateJobCheck(client, id, output) — sends only {owner, repo, check_run_id, output} (no status/conclusion).
  • publishGitHubCheck reworked: tries the new path; on PATCH failure, invalidates the cache and falls through to the existing listForRef + create/update legacy path.
  • scan/src/annotations.ts — fallback error message updated to mention actions: read.
  • README.md — three permissions: examples now include actions: read; branch-protection section gets a note explaining the new check name comes from the workflow job (set jobs.<id>.name: for stability).

New required permission

permissions:
  contents: write
  pull-requests: write
  checks: write
  actions: read   # NEW — needed to resolve the workflow job's check-run ID

Breaking change

Users whose branch-protection rule requires a check named exactly Qodana for <Linter> must update the rule to require the workflow job's check name (the job's YAML key, or whatever they set via jobs.<id>.name:). README documents this.

Test plan

  • npm run build — clean
  • npm run -w scan lint — 0 errors
  • npm run -w scan test — 37 passed, 1 skipped (was 26 baseline; +11 new tests)
  • scan/dist/index.js re-bundled by the local pre-commit hook
  • Manual: sandbox repo with two pull_request workflows — confirm Qodana summary appears only under the workflow that invoked the action (no orphan check); cross-check check_suite_id via gh api
  • Manual: confirm fallback path (without actions: read permission) still produces a check-run with the legacy orphan-grouping bug, plus a clear warning telling the user to add the permission

New tests

  • Exact GITHUB_JOB match — PATCH payload carries no status/conclusion
  • Pagination — candidate found on page 2+
  • Matrix prefix match with in_progress tie-break
  • Matrix with multiple concurrently in_progress legs — RUNNER_NAME disambiguates
  • name: override → RUNNER_NAME fallback
  • GITHUB_ACTIONS unset → legacy fallback
  • Missing GITHUB_RUN_ID → legacy fallback (no API call)
  • paginate rejects → warning + legacy fallback
  • No matching job → warning + legacy fallback, lookup cached
  • Caching: second publishGitHubCheck reuses cached job ID, no second API call
  • PATCH failure → cache invalidated, subsequent chunks skip PATCH entirely

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown

Qodana for JVM

1 new problem were found

Inspection name Severity Problems
Grammar ◽️ Notice 1

💡 Qodana analysis was run in the pull request mode: only the changed files were checked
☁️ View the detailed Qodana report

Detected 187 dependencies

Third-party software list

This page lists the third-party software dependencies used in qodana

Dependency Version Licenses
@actions/artifact 2.3.2 MIT
@actions/cache 4.1.0 MIT
@actions/core 1.11.1 MIT
@actions/exec 1.1.1 MIT
@actions/github 5.1.1 MIT
@actions/github 6.0.1 MIT
@actions/glob 0.1.2 MIT
@actions/http-client 2.2.1 MIT
@actions/io 1.1.3 MIT
@actions/tool-cache 2.0.2 MIT
@azure/abort-controller 1.1.0 MIT
@azure/abort-controller 2.1.2 MIT
@azure/core-auth 1.7.2 MIT
@azure/core-util 1.9.0 MIT
@azure/ms-rest-js 2.7.0 MIT
@azure/storage-blob 12.17.0 MIT
@fastify/busboy 2.1.1 MIT
@gitbeaker/core 43.8.0 MIT
@gitbeaker/requester-utils 43.8.0 MIT
@gitbeaker/rest 43.8.0 MIT
@isaacs/fs-minipass 4.0.1 ISC
@octokit/auth-token 2.5.0 MIT
@octokit/core 3.6.0 MIT
@octokit/core 5.2.0 MIT
@octokit/core 7.0.6 MIT
@octokit/endpoint 6.0.12 MIT
@octokit/endpoint 9.0.6 MIT
@octokit/graphql 4.8.0 MIT
@octokit/openapi-types 12.11.0 MIT
@octokit/openapi-types 20.0.0 MIT
@octokit/openapi-types 27.0.0 MIT
@octokit/openapi-webhooks-types 12.1.0 MIT
@octokit/plugin-paginate-rest 14.0.0 MIT
@octokit/plugin-paginate-rest 2.21.3 MIT
@octokit/plugin-paginate-rest 9.2.2 MIT
@octokit/plugin-request-log 1.0.4 MIT
@octokit/plugin-request-log 6.0.0 MIT
@octokit/plugin-rest-endpoint-methods 10.4.1 MIT
@octokit/plugin-rest-endpoint-methods 17.0.0 MIT
@octokit/plugin-rest-endpoint-methods 5.16.2 MIT
@octokit/plugin-retry 3.0.9 MIT
@octokit/request-error 2.1.0 MIT
@octokit/request-error 5.1.1 MIT
@octokit/request-error 7.0.0 MIT
@octokit/request 5.6.3 MIT
@octokit/request 8.4.1 MIT
@octokit/rest 22.0.1 MIT
@octokit/types 12.6.0 MIT
@octokit/types 13.10.0 MIT
@octokit/types 14.1.0 MIT
@octokit/types 16.0.0 MIT
@octokit/types 6.41.0 MIT
@octokit/webhooks-methods 6.0.0 MIT
@octokit/webhooks 14.2.0 MIT
@protobuf-ts/plugin-framework 2.9.4 Apache-2.0
BSD-3-Clause
@protobuf-ts/plugin 2.9.4 Apache-2.0
@protobuf-ts/protoc 2.9.4 Apache-2.0
@protobuf-ts/runtime-rpc 2.11.1 Apache-2.0
@protobuf-ts/runtime 2.11.1 Apache-2.0
BSD-3-Clause
@qodana/action 1.0.0 Apache-2.0
@qodana/ci-common 1.0.0 Apache-2.0
@qodana/gitlab 1.0.0 Apache-2.0
@qodana/vsts 1.0.0 Apache-2.0
@types/adm-zip 0.5.7 MIT
@types/node 16.18.126 MIT
@types/node 25.5.2 MIT
@types/sarif 2.1.7 MIT
@types/semver 5.5.0 MIT
@types/uuid 3.4.13 MIT
abort-controller 3.0.0 MIT
adm-zip 0.5.17 MIT
archiver-utils 5.0.2 MIT
archiver 7.0.1 MIT
argparse 1.0.10 MIT
async 3.2.5 MIT
asynckit 0.4.0 MIT
axios 1.14.0 MIT
azure-devops-node-api 15.1.2 MIT
azure-pipelines-task-lib 5.2.8 MIT
azure-pipelines-tasks-utility-common 3.272.0 MIT
azure-pipelines-tool-lib 2.0.12 MIT
b4a 1.6.6 Apache-2.0
balanced-match 1.0.2 MIT
bare-events 2.2.2 Apache-2.0
before-after-hook 2.2.3 Apache-2.0
binary 0.3.0 MIT
bottleneck 2.19.5 MIT
brace-expansion 2.0.2 MIT
buffer-crc32 1.0.0 MIT
buffers 0.1.1 MIT
call-bind-apply-helpers 1.0.2 MIT
chainsaw 0.1.0 MIT
chownr 3.0.0 BlueOak-1.0.0
combined-stream 1.0.8 MIT
compress-commons 6.0.2 MIT
core-util-is 1.0.3 MIT
crc-32 1.2.2 Apache-2.0
crc32-stream 6.0.0 MIT
delayed-stream 1.0.0 MIT
deprecation 2.3.1 ISC
des.js 1.1.0 MIT
dunder-proto 1.0.1 MIT
es-define-property 1.0.1 MIT
es-errors 1.3.0 MIT
es-object-atoms 1.1.1 MIT
es-set-tostringtag 2.1.0 MIT
esprima 4.0.1 BSD-2-Clause
event-target-shim 5.0.1 MIT
fast-fifo 1.3.2 MIT
follow-redirects 1.15.11 MIT
form-data 2.5.1 MIT
form-data 4.0.5 MIT
function-bind 1.1.2 MIT
get-intrinsic 1.3.0 MIT
get-proto 1.0.1 MIT
gopd 1.2.0 MIT
has-symbols 1.1.0 MIT
has-tostringtag 1.0.2 MIT
hasown 2.0.2 MIT
immediate 3.0.6 MIT
inherits 2.0.4 ISC
is-plain-object 5.0.0 MIT
is-stream 2.0.1 MIT
isarray 1.0.0 MIT
js-md4 0.3.2 MIT
js-yaml 3.14.2 MIT
jszip 3.10.1 GPL-3.0-or-later
MIT
jwt-decode 3.1.2 MIT
lie 3.3.0 MIT
math-intrinsics 1.1.0 MIT
mime-db 1.52.0 MIT
mime-types 2.1.35 MIT
minimatch 3.1.5 ISC
minimatch 5.1.6 ISC
minimist 1.2.8 MIT
minipass 7.1.2 ISC
minizlib 3.1.0 MIT
mkdirp 0.5.6 MIT
node-fetch 2.7.0 MIT
normalize-path 3.0.0 MIT
once 1.4.0 ISC
pako 1.0.11 MIT
Zlib
picomatch-browser 2.2.6 MIT
process-nextick-args 2.0.1 MIT
proxy-from-env 2.1.0 MIT
qs 6.15.0 BSD-3-Clause
queue-tick 1.0.1 MIT
rate-limiter-flexible 8.3.0 ISC
readable-stream 2.3.8 MIT
readable-stream 4.5.2 MIT
readdir-glob 1.1.3 Apache-2.0
safe-buffer 5.1.2 MIT
sax 1.3.0 ISC
semver-compare 1.0.0 MIT
semver 5.7.2 ISC
semver 6.3.1 ISC
setimmediate 1.0.5 MIT
shell-quote 1.8.3 MIT
sprintf-js 1.0.3 BSD-3-Clause
streamx 2.16.1 MIT
string_decoder 1.1.1 MIT
tar-stream 3.1.7 MIT
tar 7.5.13 BlueOak-1.0.0
tr46 0.0.3 MIT
traverse 0.3.9 MIT
tslib 1.14.1 0BSD
tslib 2.8.1 0BSD
tunnel 0.0.6 MIT
typed-rest-client 1.8.11 MIT
typed-rest-client 2.1.0 MIT
typed-rest-client 2.2.0 MIT
typescript 3.9.10 Apache-2.0
underscore 1.13.6 MIT
undici-types 7.18.2 MIT
undici 5.29.0 MIT
universal-user-agent 6.0.1 ISC
unzip-stream 0.3.4 MIT
util-deprecate 1.0.2 MIT
uuid 3.4.0 MIT
uuid 8.3.2 MIT
webidl-conversions 3.0.1 BSD-2-Clause
whatwg-url 5.0.0 MIT
xcase 2.0.1 MIT
xml2js 0.5.0 MIT
xmlbuilder 11.0.1 MIT
yallist 5.0.0 BlueOak-1.0.0
zip-stream 6.0.1 MIT
Contact Qodana team

Contact us at qodana-support@jetbrains.com

@tiulpin

Copy link
Copy Markdown
Member

I was waiting for this one ! :) thanks a lot!

@bindreams
Anna Zhukova (bindreams) marked this pull request as ready for review May 27, 2026 14:25
@bindreams

Copy link
Copy Markdown
Author

Ready for review!

Comment thread scan/src/utils.ts Outdated
Comment on lines +671 to +674
// PATCH can fail in fork-PR contexts with a restricted token, on a
// stale ID, or with mismatched app identity. Fall through to the
// legacy create/update-by-name path so the user still gets a check-run.
core.warning(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • do you have a run of the action with @azhukova/QD-14800 version?
  • can we move those comments into the function comments / collapse them a bit?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrote the comments and created a demonstration run over at JetBrains/qodana-kotlin-cli#18. You can see how it was before the fix in a screenshot attached to the YT ticket.

Comment thread action.yaml
required: false
default: ""
deprecationMessage: 'This option is for development purposes only. Do not use it in production.'
_job-check-run-id:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we sure there's no way without internal parameter to avoid this? we could test / explore existing APIs more, but if there's no other way, I would accept this solution and everything else looks good to me

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other solutions but they are all subpar. You could use the API to iterate over the list of current runs and try and find the one that's yours according to some heuristics based on name and running status - I thought this is too fragile. The solution currently in place is borrowed from https://github.com/qoomon/actions--context/blob/main/action.yaml

There are two more solutions:

  1. Restructure qodana-action into a composite action and evaluate the expression completely inside;
  2. Wait for Add JOB_ env vars actions/runner#4053 which has no downsides.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants