Skip to content

Stabilize frontmatter hash across LF/CRLF newline conventions#17153

Merged
pelikhan merged 3 commits intomainfrom
copilot/stabilize-frontmatter-hash
Feb 20, 2026
Merged

Stabilize frontmatter hash across LF/CRLF newline conventions#17153
pelikhan merged 3 commits intomainfrom
copilot/stabilize-frontmatter-hash

Conversation

Copy link
Contributor

Copilot AI commented Feb 20, 2026

Cross-platform contributors producing CRLF files could get different frontmatter-hash values for semantically identical workflows, causing spurious lock-file diffs in CI.

Changes

  • pkg/parser/frontmatter_hash.go: Normalize CRLF→LF at the entry point of extractFrontmatterAndBodyText before any splitting or processing, making the guarantee explicit rather than incidental:

    // Normalize CRLF to LF so that files with Windows line-endings produce the
    // same frontmatter text (and therefore the same hash) as equivalent LF files.
    content = strings.ReplaceAll(content, "\r\n", "\n")
  • pkg/parser/frontmatter_hash_consistency_test.go: Two new regression tests:

    • TestHashConsistency_LFvsCRLF — covers simple, complex, template-expression, and inlined-imports frontmatter variants
    • TestHashConsistency_LFvsCRLF_WithImports — covers workflows where both main and imported files differ only in line endings

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw GO111MODULE 64/bin/go /tmp/go-build365--quiet -tes�� -test.paniconexit0 -test.v=true (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha -json GO111MODULE 8651185/b354/vet.cfg GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet GOINSECURE GOMOD GOMODCACHE /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha -json GO111MODULE ache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env 1929-27093/test-491289856/.github/workflows GO111MODULE .cfg GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha /ref/tags/v8 GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha 8651185/b001/_pkg_.a GO111MODULE .cfg GOINSECURE GOMOD GOMODCACHE go env runs/20260220-131929-27093/test-go1.25.0 GO111MODULE /opt/hostedtoolcache/go/1.25.0/x-nolocalimports GOINSECURE GOMOD GOMODCACHE go (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha ere go /usr/bin/git rVNY/GPrPAFzu0qKgit GO111MODULE 64/bin/go git conf�� user.name Test User /usr/bin/git che/go-build/e4/git **/*.cjs 64/bin/go git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE x_amd64/vet GOINSECURE GOMOD GOMODCACHE x_amd64/vet (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha -json GO111MODULE /usr/bin/git GOINSECURE GOMOD GOMODCACHE git rev-�� --git-dir GOPROXY /usr/bin/git GOSUMDB GOWORK 64/bin/go git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha /tmp/gh-aw-test-runs/20260220-131929-27093/test-1735348095 rev-parse /usr/bin/git @{u} GOPROXY 64/bin/go git rev-�� --show-toplevel 0qIfHov/c9W7SApZkXp6pAimNyCI /opt/hostedtoolcache/node/24.13.0/x64/bin/node /tmp/go-build105git -trimpath 64/bin/go /opt/hostedtoolcache/node/24.13.0/x64/bin/node (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha GOSUMDB GOWORK 64/bin/go GOINSECURE GOMOD GOMODCACHE go env ck 'scripts/**/*GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD erignore go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha ck 'scripts/**/*GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha -json GO111MODULE /opt/hostedtoolcache/go/1.25.0/x/tmp/gh-aw-git-clone-3736678224 GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE .cfg GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha -json GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE /opt/hostedtoolcache/go/1.25.0/x64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/1/artifacts
    • Triggering command: /usr/bin/gh gh run download 1 --dir test-logs/run-1 GO111MODULE x_amd64/compile GOINSECURE GOMOD GOMODCACHE 51RFpkM/6HJcGCc2Qt5lJ-lnkNpl env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/12345/artifacts
    • Triggering command: /usr/bin/gh gh run download 12345 --dir test-logs/run-12345 GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env hub/workflows GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/12346/artifacts
    • Triggering command: /usr/bin/gh gh run download 12346 --dir test-logs/run-12346 GO111MODULE x_amd64/link GOINSECURE GOMOD GOMODCACHE x_amd64/link env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE -j/NEbzTRrGx3Hf7l9Ai1Xn/DcrjArqgCZ0F2zfYcPZo (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/2/artifacts
    • Triggering command: /usr/bin/gh gh run download 2 --dir test-logs/run-2 GO111MODULE x_amd64/vet GOINSECURE GOMOD GOMODCACHE x_amd64/vet env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/3/artifacts
    • Triggering command: /usr/bin/gh gh run download 3 --dir test-logs/run-3 GO111MODULE x_amd64/vet GOINSECURE GOMOD GOMODCACHE x_amd64/vet env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/4/artifacts
    • Triggering command: /usr/bin/gh gh run download 4 --dir test-logs/run-4 GO111MODULE x_amd64/compile GOINSECURE GOMOD GOMODCACHE x_amd64/compile env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/5/artifacts
    • Triggering command: /usr/bin/gh gh run download 5 --dir test-logs/run-5 GO111MODULE x_amd64/link GOINSECURE GOMOD GOMODCACHE x_amd64/link env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE i8/wl1tDiMkVqxwJLLHK96I/vmtrAJLFremote.origin.url (http block)
  • https://api.github.com/repos/github/gh-aw/actions/workflows
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE ache/go/1.25.0/xGO111MODULE env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
    • Triggering command: /usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 100 GOMOD GOMODCACHE go env RMqz/6dWRFSbjw9NGOSUMDB GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE 9977308/b387/importcfg (http block)
    • Triggering command: /usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 6 GOMOD GOMODCACHE x_amd64/vet env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env 491289856/.github/workflows GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/nonexistent/repo/actions/runs/12345
    • Triggering command: /usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion GOINSECURE GOMOD GOMODCACHE go env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/owner/repo/actions/workflows
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/bin/go GOINSECURE GOMOD GOMODCACHE erignore env -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path --repo owner/repo 64/bin/go GOINSECURE GOMOD GOMODCACHE ache/go/1.25.0/xGO111MODULE env 9977308/b328/_pkGOINSECURE GO111MODULE 64/bin/go GOINSECURE b/gh-aw/pkg/pars--norc GOMODCACHE go (http block)
  • https://api.github.com/repos/owner/repo/contents/file.md
    • Triggering command: /tmp/go-build3658651185/b380/cli.test /tmp/go-build3658651185/b380/cli.test -test.testlogfile=/tmp/go-build3658651185/b380/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true GOINSECURE GOMOD GOMODCACHE go env ck 'scripts/**/*GOINSECURE GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE go (http block)
  • https://api.github.com/repos/test-owner/test-repo/actions/secrets
    • Triggering command: /usr/bin/gh gh api /repos/test-owner/test-repo/actions/secrets --jq .secrets[].name -json GO111MODULE 64/bin/go GOINSECURE GOMOD GOMODCACHE erignore env e=false GO111MODULE 64/bin/go GOINSECURE %H %ct %D GOMODCACHE go (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Stabilize frontmatter hash across LF/CRLF newline conventions</issue_title>
<issue_description>## Problem
Cross-platform contributors can generate different frontmatter-hash values for semantically identical workflows when line endings differ (LF vs CRLF), causing noisy lock diffs and reducing trust in deterministic compile output.

Why Now

Mixed macOS/Linux/Windows workflows are common in active repos, and hash drift adds recurring review friction in CI lock-file updates.

Current Behavior Is Insufficient

Hash generation should be content-semantic, but newline-representation differences can leak into hash inputs in some frontmatter/import merge paths.

Why Code Change

This needs canonicalization in hash construction and regression tests; docs/policy cannot enforce contributor editor settings reliably.

Scope / Codepaths

  • pkg/parser/frontmatter_hash.go
  • pkg/parser/frontmatter_hash_consistency_test.go
  • pkg/cli/hash_command.go

Acceptance / Validation

  • Add regression tests that assert LF and CRLF variants produce identical hash values.
  • Keep existing deterministic behavior for same-content same-newline inputs.
  • Validate via parser/hash unit tests and repo-required checks.

Repro Evidence Packet

  • Version/commit under test: origin/main as of 2026-02-20.
  • Environment: macOS (Darwin), Go toolchain from repo setup.
  • Minimal repro:
    1. Create two equivalent workflow files differing only in newline convention.
    2. Compute hash for each via parser hash path.
  • Expected: identical hash.
  • Actual: hash differs in at least one merge/string path (to be locked by test and fixed).
    </issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix frontmatter hash inconsistencies across newline conventions Stabilize frontmatter hash across LF/CRLF newline conventions Feb 20, 2026
Copilot AI requested a review from pelikhan February 20, 2026 13:24
@pelikhan pelikhan marked this pull request as ready for review February 20, 2026 13:31
Copilot AI review requested due to automatic review settings February 20, 2026 13:31
@pelikhan pelikhan merged commit 0dd08cc into main Feb 20, 2026
56 checks passed
@pelikhan pelikhan deleted the copilot/stabilize-frontmatter-hash branch February 20, 2026 13:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Stabilizes frontmatter-hash generation across platforms by ensuring workflow files with CRLF line endings produce the same hash as semantically identical LF files, preventing noisy lock-file diffs in CI.

Changes:

  • Normalize \r\n\n at the start of extractFrontmatterAndBodyText to canonicalize newline handling before any splitting/joining.
  • Add regression tests covering LF vs CRLF hash consistency for multiple frontmatter variants and import scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
pkg/parser/frontmatter_hash.go Canonicalizes CRLF line endings early to prevent stray \r characters from affecting the hashed frontmatter/body text.
pkg/parser/frontmatter_hash_consistency_test.go Adds LF-vs-CRLF regression tests, including coverage for workflows that use imports.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

Stabilize frontmatter hash across LF/CRLF newline conventions

3 participants