bug#81520: Add pre-receive hook to Savannah to reject LLM-encumbered commits

Sean Whitton <[email protected]>
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
Jim Porter [17/Aug  1:07pm -07] wrote:
> Subject: [PATCH 1/2] Simplify commit msg file hook
>
> * build-aux/git-hooks/commit-msg-files.awk (get_commit_changes): Use
> "git diff-tree" to simplify parsing of touched files.
>
> * build-aux/git-hooks/pre-push: Use a simpler invocation of "git
> rev-list" to get all commits to be pushed.
> ---
>  build-aux/git-hooks/commit-msg-files.awk | 20 ++++----
>  build-aux/git-hooks/pre-push             | 60 ++++++------------------
>  2 files changed, 23 insertions(+), 57 deletions(-)

I'm uncomfortable with large refactorings like this without tests to
prove that they don't also introduce a functional change.  Is it not
possible to make the substantive changes you want to make without also
including these refactorings?  Alternatively, perhaps you could write
some tests.

> +** LLM usage policies
> +
> +The GNU project doesn't allow LLM-generated outputs in contributions.
> +This applies to all code, documentation, test data, and commit messages.
> +
> +This is enforced by a git hook that runs when committing or pushing
> +changes.  This hook checks for the names of common LLMs (or their
> +harnesses), as well as the following fields in the commit message:
> +"Assisted-by:", "AI-assisted:", and "Claude-session:".
> +

The text should make it clear that it's a provisional policy pending
work with the FSF is doing with lawyers.

> -    if (status != 0) {
> -      print "Commit aborted; please see the file 'CONTRIBUTE'"
> -    }
>      exit status
>    }
>  ' <"$1"
> +lint_result=$?
> +
> +AUTHOR=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/\1/p')
> +COMMITTER=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/\1/p')
> +(echo "Author: $AUTHOR"; echo "Committer: $COMMITTER"; cat $1) | \
> +  awk -f "$HOOKS_DIR"/commit-msg-llm-check.awk
> +llm_result=$?
> +
> +if [ "$llm_result" -ne 0 ]; then
> +  echo; cat "$HOOKS_DIR"/commit-msg-llm-error.txt; echo
> +fi
> +
> +if [ "$lint_result" -ne 0 -o "$llm_result" -ne 0 ]; then
> +  echo "Commit aborted; please see the file 'CONTRIBUTE'"
> +  exit 1
> +fi

I don't think we should prioritise the LLM failure like this.
I.e. I think you should have only the latter of these if statements.

I don't think we need a special .txt file here.
The combination of CONTRIBUTE, AGENTS.md and CLAUDE.md is enough.

> +function get_short_sha(commit_sha) {
> +  cmd = ("git rev-parse --short " commia_sha)

Type "commia".  Surprised this didn't come up in testing.

Probably the refactoring to prepare a short SHA in this more principled
way (thanks) should be its own commit.

> -BEGIN {
> -  if (reason == "pre-push")
> -    verbose = 1
> -}
> -
>  /^[a-z0-9]{40}$/ {
> -  if (! check_commit_msg_files($0, verbose)) {
> +  if (! check_commit_msg_files($0, verbose))
>      status = 1
> -  }
>  }
>
>  END {
> -  if (status != 0) {
> -    if (reason == "pre-push")
> -      error_msg = "Push aborted"
> -    else
> -      error_msg = "Bad commit message"
> -    printf("%s; please see the file 'CONTRIBUTE'\n", error_msg)
> -  }
>    exit status
>  }

Not sure why you're making these structural changes.

> +# Check for fields that commonly indicate LLM usage.
> +tolower($0) ~ /^(assisted-by|ai-assisted):/ { report_error($0) }
> +tolower($0) ~ /^claude-session:/            { report_error($0) }
> +
> +# Check for common LLMs/harnesses.  We purposefully exclude "Claude"
> +# here because it's also a human name.
> +tolower($0) ~ /openai/    { report_error($0) }
> +tolower($0) ~ /anthropic/ { report_error($0) }
> +tolower($0) ~ /copilot/   { report_error($0) }

Maybe we could match 'claude' case-sensitively?

> +  # Check for LLM attributions in all outgoing commits.  We do this in a
> +  # separate pass from above so that the output is kept separate.
> +  get_outgoing_shas "$local_sha" | (while read outgoing_sha; do
> +    short_sha=$(git rev-parse --short "$outgoing_sha")
> +    get_commit_msg "$outgoing_sha" | \
> +      $awk -v commit_sha="$short_sha" -f "$HOOKS_DIR"/commit-msg-llm-check.awk
> +    status=$(expr "$?" "|" "$status")
> +  # Since commands in pipelines all run in their own subshells,
> +  # explicitly exit with the overall status so the parent shell knows
> +  # the result.
> +  done; exit "$status")
> +  llm_result=$?
> +done
> +
> +if [ "$llm_result" -ne 0 ]; then
> +  echo; cat "$HOOKS_DIR"/commit-msg-llm-error.txt; echo
> +fi
> +
> +if [ "$files_result" -ne 0 -o "$llm_result" -ne 0 ]; then
> +  echo "Push aborted; please see the file 'CONTRIBUTE'\n"
> +  exit 1
> +fi
> +
> +exit 2

Again I don't think LLM failures deserve to be treated so separately.

-- 
Sean Whitton
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.