Skip to content

pinact: fix SARIF fixes corrupting lines on apply - #1154

Merged
EliSchleifer merged 4 commits into
mainfrom
pinact-fix-sarif-line-corruption
Aug 1, 2026
Merged

pinact: fix SARIF fixes corrupting lines on apply#1154
EliSchleifer merged 4 commits into
mainfrom
pinact-fix-sarif-line-corruption

Conversation

@EliSchleifer

Copy link
Copy Markdown
Member

Summary

The pinact linter's autofix corrupts every uses: line it pins. Running trunk check --fix (or fmt-on-save) turns:

uses: actions/checkout@v4

into:

uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0        uses: actions/checkout@v4

The pinned ref is written, but the original uses: …@v4 is left dangling on the same line (falling past the # into a comment). Every pinned action is affected.

Root cause

The plugin passes pinact's SARIF straight to Trunk's fix applier. pinact describes each fix with a line-only region:

"deletedRegion": { "startLine": 40 },
"insertedContent": { "text": "      uses: actions/checkout@<sha> # v4.4.0" }

Trunk reads a deletedRegion with no endLine/columns as a zero-width insertion point at column 1, so it inserts the pinned line and never deletes the original — concatenating both. The ruff/sqlfluff/etc. converters in this repo don't hit this because they always emit a fully-specified region (startLine+startColumn+endLine+endColumn).

Fix

pinact_run.py now post-processes pinact's SARIF before emitting it (normalize_fix_regions): each fix's deletedRegion is widened to span the whole original line (startColumn: 1endColumn: len(line)+1, endLine = startLine), matching the convention Trunk applies correctly. It's a no-op for any region pinact ever fully specifies (guards on endColumn/endLine/charLength/charOffset).

Coverage gap this exposes

None of the existing snapshot tests ever applied a pinact fix — the driver's runCheck forces -n (--no-fix), so every snapshot only covered parse-errors. Added a regression test that applies a real fix and asserts the line is pinned to a SHA without the concatenation corruption. Verified it fails without the pinact_run.py change and passes with it. It's online-gated (a resolvable SHA is required) and version-independent (does not snapshot the volatile SHA).

Test plan

  • trunk fmt + trunk check on both changed files — no new issues
  • New regression test passes with the fix, fails without it
  • End-to-end: applied against a repo with ~170 pinned uses: lines — 0 mangled, byte-for-byte identical to a direct pinact run
  • Pre-push repo tests pass (232/232)

Made with Cursor

EliSchleifer and others added 2 commits July 31, 2026 14:59
pinact emits each fix's deletedRegion with only startLine (no endLine or
columns). Trunk's fix applier reads that as a zero-width insertion at
column 1, so it prepends the pinned `uses:` and never deletes the
original line — concatenating both onto one line (the leftover falls past
the `#` into a comment). Every pinned action was mangled on apply.

Normalize pinact's SARIF in pinact_run.py before emitting it: widen each
fix's deletedRegion to span the whole original line (startColumn 1 ->
endColumn len+1, endLine = startLine), matching the fully-specified
region convention the ruff/sqlfluff converters already rely on. No-op for
any region pinact fully specifies.

Add a regression test that applies a real fix (the existing snapshot
tests never do — runCheck forces --no-fix) and asserts the line is pinned
to a SHA without the concatenation corruption. Fails without the fix.

Co-authored-by: Cursor <cursoragent@cursor.com>
The end-to-end fix test is token-gated (pinact only pins online, resolving
tags to SHAs via the GitHub API), so it is skipped in CI without a token.
Add offline tests that drive normalize_fix_regions directly via python3 —
covering the widen case plus the two guards (already-specified region,
out-of-range line) — so the invariant is locked on every run with no token
or network. Also assert the online fix leaves exactly one `uses:` (the
corruption doubled it).

Co-authored-by: Cursor <cursoragent@cursor.com>
@trunk-io

trunk-io Bot commented Jul 31, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

EliSchleifer and others added 2 commits July 31, 2026 15:25
Only backfill what a deletedRegion is missing instead of overwriting it
with a full-line span: preserve an explicit startColumn/endLine and infer
endColumn to the end of the region's end line. Skip regions that already
carry an endColumn or an offset-based span. Adds partial-region tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
CI's eslint-plugin-jest flags every expect() inside a conditionalTest()
callback as jest/no-standalone-expect, since the rule doesn't know the
repo's it()/it.skip() wrapper. Register it via additionalTestBlockFunctions
so expects in conditionalTest bodies are valid.

Co-authored-by: Cursor <cursoragent@cursor.com>
@EliSchleifer
EliSchleifer requested a review from TylerJang27 July 31, 2026 22:42
@trunk-io

trunk-io Bot commented Aug 1, 2026

Copy link
Copy Markdown

😎 Merged manually by @EliSchleifer - details.

@EliSchleifer
EliSchleifer merged commit d1e3af5 into main Aug 1, 2026
13 checks passed
@EliSchleifer
EliSchleifer deleted the pinact-fix-sarif-line-corruption branch August 1, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant