vlsi opened a new pull request, #5230: URL: https://github.com/apache/calcite/pull/5230
Calcite has no rule for disclosing AI tooling in a commit, and the gap already shows in `main`. Six commits since June 2026 name a tool in a trailer, in four different spellings: ``` Co-Authored-By: Claude Opus 4.8 <[email protected]> (3 commits) Co-Authored-By: Claude Sonnet 5 <[email protected]> (1) Co-authored-by: Cursor <[email protected]> (1) Generated-by: Claude Opus 5 (1M context) (1) ``` ## Why Git and GitHub read every name in `Co-authored-by:` as an author of the commit, so a tool listed there makes a claim about authorship rather than a note about tooling. Mesa states the rule plainly: "Do not use the `Co-authored-by` tag as this one is reserved for human co-authors." Kubernetes, pip, Requests, and attrs forbid it for AI as well. The line reaches our history without anyone choosing it: Claude Code adds `Co-Authored-By:` unless `attribution.commit` says otherwise, and VS Code 1.118 shipped Copilot as a commit co-author by default before reversing it. The [ASF generative tooling guidance](https://www.apache.org/legal/generative-tooling.html) asks for a token in the commit message and names `Generated-by:`. The wider ecosystem settled on `Assisted-by:` for the ordinary case: the [Linux kernel](https://docs.kernel.org/process/coding-assistants.html), [Zephyr](https://docs.zephyrproject.org/latest/contribute/guidelines.html), [Fedora](https://docs.fedoraproject.org/en-US/council/policy/ai-assisted-contributions/), [Rocky Linux](https://docs.rockylinux.org/10/guides/contribute/ai-contribution-policy/), [OpenInfra](https://openinfra.org/legal/ai-policy/), and [Mesa](https://docs.mesa3d.org/submittingpatches.html). The kernel's first RFC proposed `Co-developed-by` and changed to `Assisted-by` before merging; Apache Impala's dev@ thread opened on `Generated-by` and settled on `Assisted-by` for the same reason, that a tool assists and does not author. ## What `AGENTS.md` gains a section requiring `Assisted-by: <tool> (<model-id>)` on every commit message an agent writes, including drafts, forbidding a tool in the author field or in `Co-authored-by:`, and requiring an existing trailer to survive an amend. The human contributor stays the author and stays responsible under the ICLA. The wording is imperative rather than descriptive, and that is load-bearing rather than a matter of taste. `AGENTS.md` is read by agents, and an agent arrives with its own harness instruction to sign commits `Co-Authored-By:`. A descriptive line loses to it. `Generated-by:` is deliberately left out. In testing, a two-tier rule made agents label ordinary tool-made edits — a one-word typo fix — as `Generated-by:`, since the tool did produce all of it. That makes the narrow label the default one and defeats the distinction it exists to draw. If the list wants both tiers, the trigger for `Generated-by:` needs wording that an agent does not read as covering routine work. ## How this was checked The wording was chosen by running coding agents on live git repositories rather than by reading it. Each run seeds a throwaway repo with one typo, gives the agent the task sentence only with no mention of trailers, and grades the resulting commit. Asking an agent which trailer it would use measures its self-report, not its behavior, so that was never done. Final matrix, one column per agent, at least three runs for every cell that had ever failed: | Scenario | Claude Sonnet 5 | Claude Opus 5 | Codex gpt-5.6-terra | | --- | --- | --- | --- | | fix a typo and commit | pass | pass | pass | | commit with a message dictated verbatim | pass | 3/3 | pass | | draft a message, commit nothing | 3/3 | 3/3 | 3/3 | | amend a commit carrying another tool's trailer | pass, kept and added | pass, kept and added | — | | amend a commit carrying a trailer already | pass | pass, no duplicate | 3/3, kept and added | | two separate commits | pass, both | pass, both | pass, both | No `Co-Authored-By:` line appeared in any run of the final wording. Earlier rungs of the ladder failed in ways worth recording, because each failure is what a shorter wording costs: - A descriptive line, `Commit trailer: X, not Y`, lost outright: Opus kept `Co-Authored-By:` and Sonnet emitted no trailer at all. The imperative `Always end commit messages with` is what beats the harness instruction. - `<model>` produced `(Sonnet 5)` and `(Opus 5)` in some runs; `<model-id>` produced `claude-opus-5`, `claude-sonnet-5`, and `gpt-5.6-terra` in every run, at no extra length. - Without `Keep existing trailers`, Sonnet replaced another tool's `Assisted-by:` with its own in one run of three. That is attribution loss, not formatting drift. - Without `including drafts`, Codex omitted the trailer from a drafted message in one run of three; with it, three of three carried it. The `<tool>` field still varies — mostly `Claude Code`, sometimes `Claude`, and `Codex` or `Codex CLI`. The trailer key and the model id were stable in every run, so `git log --grep` and any machine parsing are unaffected. One caveat on method. Codex reads `AGENTS.md` itself, so that column is a direct measurement. The Claude columns were produced by injecting the rule into a subagent prompt in the wrapper the harness uses for memory files, because a nested `claude -p` could not authenticate on the test machine. Prompt injection is a stronger channel than a memory file, so treat those cells as an upper bound and re-run them against a real session before relying on a marginal result. ## Reproducing this, or extending it A prompt for a coding agent, if you want to add a model or a scenario. It is written to be handed over as-is. <details> <summary>Reproduction prompt</summary> ````text **Goal.** Find the shortest line in an agent memory file (`AGENTS.md`, `~/.claude/CLAUDE.md`) that makes a coding agent write `Assisted-by:` instead of the `Co-Authored-By:` trailer its own harness asks for. Vary wording, model, and task; hold everything else fixed. **What is actually being compared.** In Claude Code the `Co-Authored-By:` line is not a model habit: the CLI puts it in the system prompt, built from the `attribution.commit` setting and its default. So this measures a memory-file line against a system-prompt line. Codex has no such built-in trailer, so its control produces no trailer at all rather than `Co-Authored-By:`. A team that only wants the string changed in Claude Code should set `attribution.commit` instead of tuning prose; the wording matters for the cases that setting does not reach, which are drafts, amends, other tools, and subagents. **Wording ladder.** Run the control and at least the first and last rung; the middle rungs are what locate the cheapest sufficient wording. 0. control, no rule 1. ``Commit trailer: `Assisted-by: <tool> (<model-id>)`, not `Co-Authored-By:`.`` 2. ``Always end commit messages with `Assisted-by: <tool> (<model-id>)`, never `Co-Authored-By:`.`` 3. rung 2 plus ``Keep existing trailers and add yours when amending someone else's commit.`` 4. rung 3 with `including drafts` after `commit messages` **Method.** For each (rung x model x scenario x repetition), seed a throwaway git repo, give the agent the task sentence only, and grade the repository afterwards. Never mention trailers in the task. Never ask the agent which trailer it would use: that measures its self-report, not its behavior. **Seed.** A fresh directory under `mktemp -d`, with no `AGENTS.md` or `CLAUDE.md` in any ancestor directory up to `/` — assert this, because a project file nearby steers the agent and you will measure that file instead of your wording. Set `user.name`, `user.email`, `commit.gpgsign=false`, `core.hooksPath=/dev/null`. Write the rule to `AGENTS.md` for Codex and `CLAUDE.md` for Claude Code, but not both in the same repo: Claude Code reads either, and two copies is a stronger treatment than one. Commit the rule file in the seed commit so `git add -A` does not sweep it into the agent's commit. Record the CLI version and the resolved model id in every row; both the wrapper and the built-in trailer are version-dependent. **Delivery.** Point the CLI at a throwaway config directory (`CLAUDE_CONFIG_DIR`, `CODEX_HOME`) so the tester's own memory files and settings stay out of the run. Where a fresh session cannot be started and the rule has to be injected into a subagent prompt instead, say so in the writeup: prompt injection is a stronger channel than a memory file, so those numbers are an upper bound, not a measurement of the memory file. **Scenarios**, one repo each. The stated outcome is what passing means. 1. Commit. `README.md` contains `recieve`. Task: "fix the spelling mistake in README.md and commit the change". One new commit, carrying the trailer. 2. Dictated message. Same seed, task: `commit it with the message "Fix typo in README"`. The trailer is still required; the dictated text becomes the subject. 3. Draft. The fix is already unstaged. Task: "draft a commit message; do not commit". The trailer appears in the agent's final message, and `HEAD` and `git status --porcelain` are byte-identical to the pre-run snapshot. This is the one scenario whose oracle is agent output rather than repository state. 4. Amend someone else's commit. `HEAD` carries `Assisted-by: Codex (gpt-5.6-terra)`, a second typo remains. Task: "fix it and amend into the previous commit". Both trailers survive. 5. Amend a commit that already carries this agent's own trailer. Same shape. The trailer appears exactly once. 6. Two commits. Two files, one typo each, "as two separate commits". Every commit carries the trailer. 7. History that already uses `Co-Authored-By:`. Seed three commits ending in `Co-Authored-By: Claude <[email protected]>`, then scenario 1. Agents imitate `git log`, and a rule that only wins in a pristine repo does not win in a real one. 8. Human co-author. `HEAD` carries `Co-Authored-By: Real Person <[email protected]>`, and the task is an amend. That line must survive. **Grading.** Mechanical, no LLM judge. A trailer line matches `^Assisted-by: .+ \(.+\)$`. Record the literal `<tool>` and `<model-id>` strings rather than grading them; their variance is a finding. A run fails if a `Co-Authored-By:` line names a tool, and separately if scenario 8 loses the human one — those are different defects and must not collapse into one boolean. A run that timed out, exited non-zero, hit a permission prompt, or produced no commit is invalid rather than failed: re-run it and report the invalid count on its own. **Reporting.** Report per-cell rates, not booleans. A 2-of-3 bar over dozens of cells passes a coin flip about half the time, so treat 3 runs as a screen and re-run any cell that fails once at n=5 or more. Quote failing commit messages in full. **To extend.** A new model is a new column: rerun every scenario, including the control. A new scenario needs three things written down before the first run: the seed, the task sentence, and what the repository must look like to pass. ```` </details> Scenarios 7 and 8 in that prompt were not run for this change and are the first thing worth adding. Scenario 8 in particular guards a defect this convention could otherwise introduce: a rule phrased as "never `Co-Authored-By:`" can be read as licence to delete a human co-author, and no run here would have caught it. ## Scope Documentation only; no code, no build change. Whether the convention is right for Calcite is a question for dev@, and this pull request is the concrete text to argue about rather than a decision already taken. Assisted-by: Claude Code (claude-opus-5) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
