Skip to content

fix(hooks): keep the sh fallback at LF on checkout so it runs on Windows - #163

Open
Matthew-Selvam wants to merge 2 commits into
ayghri:mainfrom
Matthew-Selvam:fix/crlf-shell-hook
Open

fix(hooks): keep the sh fallback at LF on checkout so it runs on Windows#163
Matthew-Selvam wants to merge 2 commits into
ayghri:mainfrom
Matthew-Selvam:fix/crlf-shell-hook

Conversation

@Matthew-Selvam

@Matthew-Selvam Matthew-Selvam commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

With core.autocrlf=true, Git checks out hooks/always-on.sh with CRLF line endings. Executing that copy with a POSIX shell fails to emit the rules and produces errors. Adding *.sh text eol=lf preserves LF on checkout and restores correct opt-in behavior.

This protects the shell script when invoked directly or by an integration configured to use it. The current hooks/hooks.json launcher invokes Node; this PR does not add automatic fallback when Node is unavailable. Failure exit codes vary by shell, so no universal code is claimed.

Scope and compatibility

Only .gitattributes changes: one explanatory comment and one attribute. No shell logic, skill rules, manifests, tests, or dependencies change. The attribute applies to tracked *.sh files; it does not impose a repository-wide line-ending policy.

Fresh checkouts receive LF shell scripts. For an existing CRLF installation, reinstall into a fresh directory, preserving any local customizations first. This change does not repair files converted to CRLF outside Git's checkout process.

Authorship and provenance

  • Human-authored
  • Autonomous agent-authored
  • Hybrid

Original contributor: Matthew-Selvam. Hermes Agent (deepseek-v4.1-flash) identified the issue, wrote the attribute, and reported reproducing it with core.autocrlf=true on macOS. The contributor reported reviewing the diff and running those checks.

The repository owner's Codex maintainer agent independently tested the PR and current main on Linux, shortened the comment, and corrected this description. The original contributor's commit is preserved; the owner reviews and merges manually.

Verification

Maintainer checks used isolated Git clones with core.autocrlf=true and false, testing both main and the PR:

  • CRLF main: both sh and Bash emitted errors and no rules, with the flag present or absent. Node continued to work. Exit codes differed between shells.
  • PR: shell script checked out as LF under both settings. sh, Bash, and Node emitted the complete skill when enabled and were silent without the opt-in flag.
  • python3 -m unittest discover -s tests -p test_always_on_hooks.py -v — failed on CRLF main; passed on LF main and both PR checkouts.
  • Applied only the PR attribute to current main 6f1f982d0a47c65899af3c5a7450b7098bc65325 in the CRLF fixture and rechecked out the shell script: normalized output was identical across sh, Bash, and Node. git ls-files --eol confirmed only the shell script's working-tree line endings changed.
  • python3 -m unittest discover -s tests -v — all 51 tests passed on that current-main-plus-fix fixture. This count refers to the integration fixture, not the older PR base.
  • git check-attr text eol -- hooks/always-on.shtext: set, eol: lf after the comment amendment.
  • git diff --check — passed after the amendment. Only comments changed after runtime verification; the tested attribute is unchanged.

Limitations: maintainer runtime checks ran on Linux with Windows-style Git checkout settings, not native Windows. PowerShell was not tested by the maintainer. No model calls or behavior evaluations were needed for this checkout-only change.

Safety and side effects

Testing used temporary clones and temporary opt-in configuration, without modifying user Git settings or accessing provider credentials. The change controls Git checkout line endings for shell files and adds no runtime network access or cost.

Labels: Target:Integrations, Author:Hybrid, bug.

`hooks/always-on.sh` ships inside the plugin and is the fallback for
environments without Node. The repo carries no .gitattributes, so a Windows
checkout with the default `core.autocrlf=true` rewrites it to CRLF and /bin/sh
dies on every line -- the carriage return becomes part of the token, so it is
read as a command:

    hooks/always-on.sh: line 8: \r: command not found
    hooks/always-on.sh: line 13: exit: 0\r: numeric argument required
    rc=255

No banner, no ruleset, and the runtime='sh' subtests in
tests/test_always_on_hooks.py fail on that checkout while the node and
PowerShell ones pass. Reproduced by cloning the repo with core.autocrlf=true
and re-checking out: 3 of 7 tests in that file failed, and the hook printed
nothing. The Node and PowerShell hooks tolerate CRLF; the shell one cannot.

Pin POSIX shell scripts to LF in the checkout. Scoped to `*.sh` deliberately:
that is the only case demonstrated, and committing a broader text/eol policy
would rewrite unrelated files on someone else's Windows checkout.
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