Skip to content

Co-Authored-By is refused as unknown-key, so the casing the flagship host writes blocks the commit #809

Description

@MongLong0214

Next action: none — fixed in #810, awaiting CI. Closes automatically on merge. schema.ts:165 held a case-sensitive Set of one spelling while types.ts documented three and matched them case-insensitively; the copy that decided was the strict one.


Summary

Co-Authored-By: in a record's trailer block is refused as unknown-key, so the commit is blocked. Co-authored-by: passes. The two spellings are the same trailer, and this repository already says so in writing — src/core/types.ts documents that all three casings reach a commit message and matches them case-insensitively. The list that validateRecord actually consults is a case-sensitive Set holding one of them.

The casing that fails is the one observed from Claude Code, which is the host with the most complete integration here.

Reproduction

Verified at 42cb0328 and at v1.2.0, macOS, git 2.50.1.

printf 'fix: t\n\nbody.\n\nLimit: a limit\nCo-Authored-By: Someone <a@b.c>\n' > cap.txt
printf 'fix: t\n\nbody.\n\nLimit: a limit\nCo-authored-by: Someone <a@b.c>\n' > low.txt

$ commitlore validate --message-file cap.txt
shape failed · references ok
6: unknown-key Co-Authored-By — got "Co-Authored-By", want "a key from SPEC §3 or X-<Name>"
commitlore: 1 violation (SPEC §6) — the message was not modified

$ commitlore validate --message-file low.txt
shape ok · references ok

And through the installed hook, in a repository with commitlore init run:

$ git commit -F cap.txt
shape failed · references ok
6: unknown-key Co-Authored-By — ...
commitlore: 1 violation (SPEC §6) — the message was not modified
# commit count unchanged — the commit did not happen

Position matters, which is why this is not hit constantly: a Co-Authored-By: line separated from the record by a blank line sits in the body, is not parsed as a trailer, and is never judged. It fails when it sits in the final trailer block — where a co-author trailer belongs.

Source boundary

Two lists of foreign trailer keys, with different matching semantics, and validation reads the stricter one.

src/core/schema.ts:165
  const WELL_KNOWN_FOREIGN_KEYS: ReadonlySet<string> =
    new Set(['Signed-off-by', 'Co-authored-by']);

src/core/schema.ts:181-184
  const isDefinedKey = (key: string): boolean =>
    (KNOWN_KEYS as readonly string[]).includes(key) ||
    EXTENSION_KEY_RE.test(key) ||
    WELL_KNOWN_FOREIGN_KEYS.has(key);        // ← Set.has is case-sensitive

against this, in the same repository:

src/core/types.ts:95-104
  Matched case-insensitively via isConventionalTrailerKey ...
  `Co-authored-by`, `Co-Authored-By` and `Co-authored-By` all reach a commit
  message from GitHub, git and various editors for the identical trailer
  (bug-issue-150's own report shows all three in one repository).

So the fact is already established here, in a comment, citing a report that observed all three casings in one repository. Ingestion acts on it; validation does not.

Present in the shipped release:

$ git show v1.2.0:src/core/schema.ts | sed -n '165p'
const WELL_KNOWN_FOREIGN_KEYS: ReadonlySet<string> = new Set(['Signed-off-by', 'Co-authored-by']);

How it was found

Two headless Claude Code runs (claude -p, plugin loaded, init --unattended) on unrelated tasks, neither told anything about CommitLore. Both emitted Co-Authored-By: unprompted. The first placed it in the body and committed. The second placed it in the trailer block alongside the record it had just captured, was refused, and worked around it by rewriting the trailer to git's casing.

That second run is the shape a user meets: capture succeeds, the record is well-formed, and the commit is then refused for the attribution line the host wrote itself.

Proposed resolution

The narrow fix is case-insensitive membership in WELL_KNOWN_FOREIGN_KEYS. The better one is that this list stops existing twice: isDefinedKey delegates to isConventionalTrailerKey, so there is one set of foreign keys with one matching rule, and a casing added for ingestion cannot fail to reach validation.

The comment above the constant pins the constraint the fix must not weaken — Constraint: must keep failing as unknown-key, which spec/fixtures/invalid/03-unknown-key.txt exists to hold. Case-insensitive matching over a two-element foreign list does not reach that fixture; delegation has to be checked against it, and against EXTENSION_KEY_RE, before it is a safe swap.

Whichever it is, the regression test is a validate over each casing this repository already lists in types.ts, since the defect is precisely that one of them was never asked.

I can send the PR if this reads correctly.

Scope note: no dist/ rebuild — aware of docs/CANONICAL-BUILD.md.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions