Skip to content

fix(cli): the commit trailer survives a hook another tool regenerates, and check says whether it did #746

Description

@blafourcade

The commit trailer installs by appending one line to prepare-commit-msg. In a repository
where another tool owns that file, the line is erased the next time that tool regenerates
it, the trailer silently stops being written, and nothing anywhere says so.

Measured, on this repository

aidd-session-trailer.sh is ours outright and nobody rewrites it. What is fragile is the
call site, and this repository shows it happening:

.git/hooks/commit-msg           Sep  2 07:06     regenerated by lefthook
.git/hooks/pre-commit           Sep  2 07:06     regenerated by lefthook
.git/hooks/pre-push             Sep  2 07:06     regenerated by lefthook
.git/hooks/prepare-commit-msg   Aug 22 11:58     untouched

Lefthook rewrote three hook files that morning. prepare-commit-msg survived for one
reason: lefthook.yml declares pre-commit, pre-push and commit-msg, and no
prepare-commit-msg job. That is a coincidence of configuration, not a guarantee — adding
such a job makes the next lefthook install erase the appended line. husky regenerates the
same way.

And nothing reports it: aidd telemetry check contains no claim about the trailer at all.
The commits keep succeeding, the journal keeps recording, and only the commit-to-session
join stops — which is exactly the shape this layer exists to refuse. An unknown is never a
zero.

The approach, and why it is general

Do not defend the call site. Re-establish it.

On session-start, where journal.cjs already pays a once-per-session cost with that
argument written down (see maybeRecordUnrecognisedPayload): if the delegate exists in the
hooks directory git actually resolves, and prepare-commit-msg no longer calls it, append
the line back.

It never asks why the line is gone, which is what makes one code path cover every case —
a regenerated hook, an overwrite by hand, a core.hooksPath move, a hook that never
existed. No third-party tool is named, so nothing here rots when their formats change.

The opt-out is the one people already know: aidd telemetry off deletes the delegate, and
the repair only ever runs when the delegate is present. Nothing is resurrected after off.

Done when

  • A prepare-commit-msg overwritten between two commits calls the delegate again after the next session start, asserted without installing lefthook or husky — the failure is reproduced by its shape, a hook file replaced, not by its brand.
  • The repair never runs when the delegate file is absent, so off stays final.
  • Its cost is one resolve plus one small read per session, and it never runs on tool-used.
  • aidd telemetry check states, each independently verifiable: where git resolves the hooks directory, whether the delegate exists and is executable, whether prepare-commit-msg calls it, whether that hook carries somebody else's content as well, and how many of the last commits actually carry AIDD-Session-Id.
  • That last claim is the one that proves the chain rather than its parts, and reads as a count, never as a bare pass.
  • A commit still succeeds when the delegate is deleted, made non-executable, or made to fail.
  • The delegate is asserted to spawn no Node and reach no network, and to end every path in exit 0.

Replacing #630's p95 box

#630 asks for p95 under 200 ms. A wall-clock threshold on a shared CI runner measures the
runner's load, not the hook, and becomes a flake generator. The property that protects a
commit is structural and is what the boxes above assert. Measure the duration once, record
the number here, and gate on the properties.

Bounds

The default case — a repository whose prepare-commit-msg nobody else generates — works
today and is proven by eight end-to-end tests in telemetry-commit-trailer.e2e.test.ts. An
erased line costs a join, never a record: sessions keep being measured, and only
commit-level attribution is lost until the next session repairs it.

Relations

Field Value
follows #630

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

    Type

    Fields

    Priority

    None yet

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions