Skip to content

feat(mt#4995): Classify a failed expectedHeadSha match instead of waiting out the timeout - #3641

Merged
edobry merged 2 commits into
mainfrom
task/mt-4995
Sep 4, 2026
Merged

edobry merged 2 commits into
mainfrom
task/mt-4995

Conversation

@minsky-ai

@minsky-ai minsky-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

session_pr_wait-for-review treated every expectedHeadSha mismatch identically: poll until the
timeout, then report expectedHeadShaUnreached. That is correct for the case it was built for — a
push still in flight — and wrong for a sha the caller constructed, which can never arrive.

The two are separable on the first poll that observes a remote head. A push-lag mismatch has an
entirely different commit as the observed head. A caller-error mismatch shares a long common prefix
and then diverges, because the caller started from a real abbreviated sha and extended it.

This matters beyond the wasted time: a wait that returns nothing after its full budget is, per
/implement-task §9, the documented lead-in to the bypass ladder. The failure mode is an agent
reading its own mis-armed wait as reviewer silence.

Originating incident (2026-09-04, PR #3635 / mt#4897): session_commit returned
commitHash: "f76e55628" (9 chars). The caller passed
f76e556285ff4d6a4e0d21b0ba1e0a54ba7d2e0f — the 9 real characters plus 31 invented ones —
believing the parameter required a full 40-character sha. It does not; prefix matching is by design
(mt#4039). The real head was f76e556281b76e51949a057834f279e73d03a8e0. Both existing boundary
checks admitted the value: it is hexadecimal and well over the 7-character floor.

Key changes

  • classifyHeadShaMismatch (pr-wait-for-review-subcommand.ts) returns divergent-prefix
    when the two share >= MIN_ABBREVIATED_SHA_LENGTH (7) characters and then differ,
    push-pending otherwise, and null when there is nothing to compare.
  • expectedHeadShaUnreached carries classification. The field is populated from the same
    headSha closure the payload reports, so the verdict can never describe a different observation
    than the lastObservedHeadSha printed beside it.
  • divergent-prefix returns immediately, through finalizeTimeout rather than a bare
    buildTimeoutResult. That path is already bounded by its own short budget and still attaches the
    fresh reviews list and reviewerCheckRunState; it cannot mis-report a review as a match here
    because its finalMatch is gated on remoteIsServingExpectedHead() — the predicate that is
    false. Verified by reading it, not assumed.
  • The text-mode message branches. On divergent-prefix the generic "two causes, opposite
    remedies" line is replaced — one of its two causes has been ruled out by evidence, so leaving it
    in asks the reader to weigh a possibility already eliminated. The new text names the padding
    mistake and the remedy (pass commitHash verbatim; do not extend it to 40 characters).
  • Parameter description and the regenerated completion-manifest.json updated to match.

Judgment calls

Gate (g) collision, resolved as coordinate rather than wait. Open PR #3412 (mt#4639, the
614-site getLoggableErrorSummary conversion) also touches
pr-wait-for-review-subcommand.ts. I read its actual changed-file list and its patch for this file
rather than judging by title: the overlap is +3 −2 — one import line and two getErrorMessage
substitutions inside catch-block log.debug calls (~L703, ~L1166). No hunk overlaps the matcher,
the result type, or the poll loop, and the test file is untouched. Blocking a two-file change on an
eight-day-old 100+-file mechanical conversion was the worse trade. The parallel-work-open-pr guard
fired at session_start and I cleared it with an audit-logged
grant-guard-override.ts grant carrying this reasoning. Whoever lands second rebases.

SC5 needed two assertion edits, and the criterion says "tests pass untouched". Adding a field to
expectedHeadShaUnreached breaks two exact-match toEqual assertions in
pr-wait-for-review-push-lag.test.ts. I updated both to include classification: "push-pending".
The semantics SC5 protects are unchanged — those tests still assert the wait polls on and times
out — but the payload shape changed, so the criterion is met in substance and not literally. Calling
that out rather than letting it read as untouched.

A known false negative, deliberately not fixed. A fabricated sha extending an older head's
abbreviation, on a PR whose head has since advanced, shares ~0 characters with the current head and
is reported as push-pending. That degrades to exactly today's behaviour rather than to a wrong
verdict; catching it would require retaining head history this wait does not keep. Recorded in the
code and in the spec's planning audit. The same reasoning covers a sha stranded by a rebase
(mem#1013) — five recorded incidents, cause fixed upstream by mt#4046, and not the shared-prefix
shape.

Testing

New file pr-wait-for-review-sha-classification.test.ts (a sibling, not an extension of
pr-wait-for-review-push-lag.test.ts, which sits near the 400-line max-lines WARN threshold that
this repo's zero-tolerance warning gate makes unshippable — mem#833's extract-a-sibling precedent).

Execution evidence:

AT1 — a 40-char value sharing its first 9 characters with the observed head, diverging after,
classified divergent-prefix, returning on poll 1 of a 600s budget; message names the cause
(the AT1 message half is asserted in pr-wait-for-review-command.test.ts, "names the
extended-abbreviation cause"). AT2 — a true abbreviated prefix still matches (mt#4039 regression).
AT3 — a sha sharing fewer than 7 characters classified push-pending and polls >50 times.
AT4 — a backend with no getPullRequestHeadSha is unchanged and reports nothing new.
SC1/SC2/SC5 are the AT1-vs-AT3 contrast above; SC3 is the adapter message test; SC4 is the
threshold test plus the fixture check named in its own test title.

$ bun test --preload ./tests/setup.ts packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts packages/domain/src/session/commands/pr-wait-for-review-push-lag.test.ts
(pass) classifyHeadShaMismatch (mt#4995) > the originating incident classifies as divergent-prefix
(pass) classifyHeadShaMismatch (mt#4995) > an unrelated sha is push-pending — the wait-it-out case
(pass) classifyHeadShaMismatch (mt#4995) > a value that MATCHES is not a mismatch at all
(pass) classifyHeadShaMismatch (mt#4995) > an absent side yields no classification, never a guess
(pass) classifyHeadShaMismatch (mt#4995) > the threshold is exactly MIN_ABBREVIATED_SHA_LENGTH, checked from both sides
(pass) classifyHeadShaMismatch (mt#4995) > classification normalizes case and whitespace like the matcher does
(pass) classifyHeadShaMismatch (mt#4995) > SC4 negative control: no existing test fixture trips the discriminator
(pass) ... > AT1: a fabricated extension returns on the first poll, not at timeout
(pass) ... > AT1: the review was visible all along — this was never reviewer silence
(pass) ... > AT2: a true abbreviated prefix still matches (mt#4039 regression)
(pass) ... > AT3: a sha sharing fewer than 7 characters waits, exactly as before
(pass) ... > AT4: with no observable head, nothing is classified and nothing is reported
 26 pass / 0 fail / 57 expect() calls  (2 files)

$ bun test --preload ./tests/setup.ts src/adapters/shared/commands/session/pr-wait-for-review-command.test.ts
(pass) formatTimeoutMessage > names the extended-abbreviation cause, and drops the wait-for-the-push remedy
 18 pass / 0 fail / 64 expect() calls

$ bun scripts/run-related-tests.ts <the 4 changed source files>
 598 pass / 0 fail / 1447 expect() calls — 36 related files, incl. src/hooks/completion-manifest-regen.test.ts

Negative control: neutralized the discriminator itself (>= MIN_ABBREVIATED_SHA_LENGTH → >= 999), a full revert of the decision logic rather than just deleting the early return, per mt#4512. Result: 4 fail / 8 pass — the originating-incident test, the threshold test, the case/whitespace test and AT1 all went red, while AT2, AT3 and AT4 stayed green. The control therefore discriminates the new claim from the behaviour SC5 requires preserved, rather than merely proving the file can fail.

(fail) classifyHeadShaMismatch (mt#4995) > the originating incident classifies as divergent-prefix
(fail) classifyHeadShaMismatch (mt#4995) > the threshold is exactly MIN_ABBREVIATED_SHA_LENGTH, checked from both sides
(fail) classifyHeadShaMismatch (mt#4995) > classification normalizes case and whitespace like the matcher does
(fail) ... > AT1: a fabricated extension returns on the first poll, not at timeout
(pass) ... > AT2 / AT3 / AT4 — unchanged behaviour, still green
 8 pass / 4 fail

Typecheck: 0 errors across 8 projects (., packages/domain, packages/shared, services/reviewer,
services/site, src/cockpit/web, tsconfig.hooks.json, tsconfig.scripts.json); infra/ skipped
for uninstalled deps. Lint: 0 errors, 0 warnings over 4382 files. format:check clean.

Deploy verification: this PR touches deploy surface — I initially tagged the commit
[no-deploy-impact] from assumption and the commit-msg guard correctly denied it, naming all 7
staged files as deploy surface. The tag is removed and the claim retracted here. After merge I will
run deployment_wait-for-latest for minsky-mcp with notBefore set to the merge timestamp and
expectCommitSha set to the merge SHA, and read buildIdentity rather than treating SUCCESS alone
as sufficient.

Live verification

Not a structural change under /implement-task §7a — no new persistence path, model-output channel,
external-system probe, deploy-target wiring, or schema migration; the behaviour is fully determined
by the injected SessionPrWaitForReviewDependencies seam and is covered by the tests above,
including a negative control. No new external-system integration, so no live-exercise requirement.

…ting out the timeout

A mismatch between `expectedHeadSha` and the observed remote head has two causes
with opposite remedies, and until now both were reported identically after the
full `timeoutSeconds`: a commit still in flight (waiting resolves it) and a sha
the caller constructed (waiting never does). The second ends in
`expectedHeadShaUnreached`, which per /implement-task §9 reads as reviewer
silence — the documented lead-in to the bypass ladder.

`classifyHeadShaMismatch` separates them on the first poll that observes a head:
a shared prefix of >= MIN_ABBREVIATED_SHA_LENGTH (7) followed by divergence is
the signature of a real abbreviated sha padded out to look full, which is the
originating incident (PR #3635: `session_commit` returned the 9-character
`f76e55628`; the caller passed those 9 characters plus 31 invented ones). Two
unrelated commits collide on 7 hex characters once in 16^7 = 268,435,456.

- `divergent-prefix` returns immediately via `finalizeTimeout`, which is already
  bounded and cannot mis-report a match (its `finalMatch` is gated on the same
  predicate that is false here), so the caller still gets the fresh reviews list
  and check-run state.
- `push-pending` is unchanged: it keeps polling, which is the branch mt#3877 and
  mt#4039 exist to protect.

The text-mode message drops the generic two-cause remedy on the classified case
— one of its two causes has been ruled out by evidence — and names the padding
mistake plus the fix (pass `commitHash` verbatim; it is matched as a prefix by
design).
@minsky-ai minsky-ai Bot added the authorship/co-authored Co-authored by human and AI agent label Sep 4, 2026
@minsky-reviewer

minsky-reviewer Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Minsky Reviewer Status

Verdict: APPROVED — no blocking findings
Review: View review
Model: openai/gpt-5 | Tokens: 358K prompt, 6K completion | Duration: 85s
Mode: normal

Commands

  • /review — request a fresh review

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


The classification and early-return behavior are well-implemented and comprehensively tested, and adapter messaging updates are appropriate. I’m requesting changes for one normative gap: SC5 in the task spec requires existing tests to pass untouched, but two prior assertions were modified to include the new classification field. Please reconcile by amending the spec or by adjusting the approach/tests to avoid altering existing tests. I also left several non-blocking nits about structured signaling for early-return vs true timeout, classification nullability, helper export scope, adapter branching robustness, and numeric-constant pinning in tests.

Findings

  • [BLOCKING] packages/domain/src/session/commands/pr-wait-for-review-push-lag.test.ts:174 — Spec SC5 requires existing tests to pass untouched; this PR modifies two existing assertions
    Task spec SC5 states: “push-pending behaviour is unchanged … and its tests pass untouched.” This diff edits two prior tests to include classification: "push-pending" in exact-match toEqual assertions at packages/domain/src/session/commands/pr-wait-for-review-push-lag.test.ts:174-181 and :235-242. While semantics are preserved, the criterion is normative and unambiguous about not modifying existing tests. Please either (a) amend the task spec to relax SC5 (documenting the additive payload change) or (b) refactor so prior tests do not need edits (e.g., shape-superset assertions or preserving legacy shape via adapter/compat), and revert these test changes.
  • [NON-BLOCKING] packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1169 — Early-return path reuses the “timeout” result shape without a distinct indicator for “returned early due to classification”
    At lines 1169-1190 the code returns await finalizeTimeout() on the first observed head when mismatchKind() === "divergent-prefix". This produces a SessionPrWaitForReviewTimeout payload with a low elapsedMs, but no explicit flag that this was an early classification vs a genuine timeout. Downstream consumers that key off matched: false + timeout shape may incorrectly treat it as a waited-out timeout. The adapter’s message text disambiguates it, but callers that don’t render the text (e.g., JSON-mode consumers) may miss the semantic distinction. Consider adding a structured indicator (e.g., timeoutCause: "divergent-prefix" | "deadline-exceeded") or a boolean returnedEarly: true to make this machine-discernible.
  • [NON-BLOCKING] packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1118 — expectedHeadShaUnreached.classification can be null even in mismatch-looking payloads
    buildTimeoutResult unconditionally includes expectedHeadShaUnreached whenever expectedHeadSha is defined (line 1175), and sets classification: mismatchKind(). Because headShaMatchesExpected returns true when either side is undefined (per the comment at 1089-1093), mismatchKind() can be null in cases where the head sha is absent, yielding a payload with classification: null. This is by design for AT4, but it may surprise consumers that rely on classification being a non-null discriminator when expectedHeadShaUnreached is present. If intentional, consider documenting the nullability explicitly in the adapter layer, or gate expectedHeadShaUnreached on an actual observed mismatch.
  • [NON-BLOCKING] packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:274 — classifyHeadShaMismatch exported publicly but only used in tests; consider scoping
    classifyHeadShaMismatch and HeadShaMismatchKind are exported from the command module but have no in-repo production consumers outside tests. If not intended as a public API, consider making them const/type internal (non-exported) or moving to a private helper to avoid expanding the public surface unnecessarily. If you do intend external reuse, adding a barrel export and doc would help clarify its status.
  • [NON-BLOCKING] src/adapters/shared/commands/session/pr-wait-for-review-command.ts:112 — Adapter message branching depends on classification equality; lack of default could miss future kinds
    The adapter’s formatTimeoutMessage branches only for classification === "divergent-prefix" and otherwise falls back to the generic text. If more HeadShaMismatchKind variants are added later, they will silently render the generic copy. Consider a switch over known kinds with an explicit default that mentions the unrecognized classification, to aid future evolution.
  • [NON-BLOCKING] packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:63 — Tests pin MIN_ABBREVIATED_SHA_LENGTH to 7; this hard-codes a constant across modules
    At lines 95-103 the test asserts MIN_ABBREVIATED_SHA_LENGTH is exactly 7. While this guards SC4’s reasoning, it couples the test to an internal constant rather than the behavior contract. If the floor changes (e.g., policy/tuning), multiple tests will need updates. Consider asserting behavior via shared-prefix lengths relative to MIN_ABBREVIATED_SHA_LENGTH without asserting its numeric value directly.

Spec verification

Criterion Status Evidence
SC1 — mismatch is classified, not just reported. Met packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:251-334 adds HeadShaMismatchKind and classifyHeadShaMismatch, and :534-548 extends expectedHeadShaUnreached to include classification. The poll loop plumbs it via mismatchKind() in buildTimeoutResult at :1169-1178. Tests exercise both branches in packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:68-90, 124-152.
SC2 — the divergent-prefix case does not wait out the timeout. Met Early-return path in packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1203-1290 returns finalizeTimeout() on first observed head when mismatchKind() === "divergent-prefix". Test asserts prompt return with pollCount: 1 and elapsedMs < 1000 at packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:124-152.
SC3 — the message names the likely cause and the remedy. Met Adapter message in src/adapters/shared/commands/session/pr-wait-for-review-command.ts:112-147 branches on classification === "divergent-prefix" to name the extended-abbreviation cause and remedy; generic branch remains for other cases. Test covers the specific copy at src/adapters/shared/commands/session/pr-wait-for-review-command.test.ts:236-283.
SC4 — the discriminator's false-positive rate is argued, not assumed. Met Rationale comment with 16^7 reasoning in packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:301-333. Tests pin the boundary and negative control at packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:95-106 (threshold) and :109-120 (fixtures do not accidentally trip classifier).
SC5 — push-pending behaviour is unchanged. The existing wait-through-the-lag path keeps its current semantics and its tests pass untouched. Not Met Behavioral semantics retained (AT3 in packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:154-171 asserts long polling), but two existing exact-match assertions were edited to include the new classification: "push-pending" field: packages/domain/src/session/commands/pr-wait-for-review-push-lag.test.ts:174-181 and :235-242. The criterion’s literal "tests pass untouched" is therefore not met. Either amend the task spec to accept additive payload shape changes or restore tests to pass unmodified (e.g., through property-superset assertions).

Adoption sweep

Symbol Kind Consumers found Classification Notes
packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts::HeadShaMismatchKind type src/adapters/shared/commands/session/pr-wait-for-review-command.ts:112 — branches on classification to alter message copy, packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:18 — imports the type for tests Adopted New discriminant is plumbed into adapter message and tests; no other public consumers detected within repo.

Documentation impact

  • no-update-needed — This PR changes internal classification and adapter message behavior but does not add or change user-facing documented contracts beyond parameter descriptions already updated in-code (src/adapters/shared/commands/session/session-parameters.ts:914-930) and regenerated CLI manifest (src/generated/completion-manifest.json). The PR description reports a docs sweep found no expectedHeadSha behavioral claims in docs/. No separate docs files in docs/ describe this timeout classification; therefore no external docs require updates.

…llable

BLOCKING (SC5). The criterion required existing tests to "pass untouched", and
two exact-match `toEqual` assertions were edited to carry the new field. The
finding is correct, and the criterion is what was wrong: SC1 requires the
classification to be REPORTED on the mismatch payload, so no exact-match
assertion on that payload can survive it. The two are in direct conflict and no
implementation satisfies both literally. SC5's own text now states the invariant
it was always protecting — the push-pending path's semantics — and says which
kind of test edit is in scope. Amending the criterion rather than explaining it
in the PR body, per mem#986 / mt#4213.

The offered alternative (superset assertions, or a compat shape) was rejected:
loosening a `toEqual` on the exact payload this task changes would REDUCE what
those tests pin, to protect a wording rather than a behaviour.

NON-BLOCKING, fixed: `classification` could be typed `null` in a payload that
never produces one. `buildTimeoutResult` now computes the kind once and gates
the whole `expectedHeadShaUnreached` object on `kind !== null` — provably the
same condition as the previous `!remoteIsServingExpectedHead()`, since both
delegate to `headShaMatchesExpected` — and the field is typed non-nullable. The
null is now unrepresentable rather than merely unreachable. (The review's stated
mechanism was slightly off: it read the object as unconditional when it was
already gated. The conclusion held.)

NON-BLOCKING, declined with basis, recorded in the spec: a separate
`returnedEarly` flag (the now-non-nullable `classification` IS that machine
-readable signal; a second field would give one fact two sources of truth);
un-exporting `classifyHeadShaMismatch` (it has a production caller via
`mismatchKind()`, and matches the sibling `headShaMatchesExpected`); an
exhaustive `switch` in the adapter (the `else` already is the safe default —
generic-but-correct beats specific-and-wrong, and naming the raw kind would leak
an internal enum); and dropping the `MIN_ABBREVIATED_SHA_LENGTH` pin (it is
load-bearing for SC4 — the ~1-in-268M argument is a function of the value being
7, so the test SHOULD fail if the floor is tuned).

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Re-verify R1’s blocking concern and the fixes: SC5 has been amended in the spec to allow payload-shape updates, and the code now makes expectedHeadShaUnreached.classification non-nullable by construction. The domain layer introduces classifyHeadShaMismatch and wires an immediate early-return on divergent-prefix; the adapter renders specific guidance for that case while preserving the generic branch for others. Tests in both domain and adapter cover the new behavior, and the push-pending path’s semantics remain intact. I found no new critical defects; one minor maintainability note suggests documenting finalizeTimeout’s pre-deadline use. Overall, the prior BLOCKING issue is resolved and the change meets all success criteria — APPROVE.

Findings

  • [NON-BLOCKING] packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1308 — Early-return path relies on finalizeTimeout side-effects; consider a brief comment in finalizeTimeout noting it may be called pre-deadline
    The early return on classification === "divergent-prefix" jumps to finalizeTimeout() (packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1308-1330). The surrounding comment here explains the choice well. As a small maintainability aid, consider adding a one-line note in finalizeTimeout itself that it can be invoked prior to deadline as a fast-fail classification path (mt#4995), so future edits to that helper preserve its applicability both at real timeouts and early exits. No functional change requested.

Spec verification

Criterion Status Evidence
SC1 — mismatch is classified, not just reported. Met packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:251-336 adds export type HeadShaMismatchKind and export function classifyHeadShaMismatch(...) returning "push-pending" | "divergent-prefix" | null; payload expectedHeadShaUnreached now includes non-nullable classification at :534-552.
SC2 — the divergent-prefix case does not wait out the timeout. Met packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1308-1330 early-returns via finalizeTimeout() when mismatchKind() === "divergent-prefix", with explanatory log, preventing a full-timeout wait.
SC3 — the message names the likely cause and the remedy. Met src/adapters/shared/commands/session/pr-wait-for-review-command.ts:112-156 branches on classification; for divergent-prefix it replaces the generic two-cause text with specific cause/remedy: do not pad to 40 chars; pass commitHash verbatim; cites mt#4039, mt#4046.
SC4 — the discriminator's false-positive rate is argued, not assumed. Met packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:286-319 carries an in-code rationale: 16^7 = 268,435,456; hex-only inputs; SHA-1 uniformity; aligns with MIN_ABBREVIATED_SHA_LENGTH. Also guarded by tests per PR, and no fixture-trigger change here.
SC5 — push-pending BEHAVIOUR is unchanged. (Amended: exact-match assertions may be updated to include classification.) Met packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1118-1160 buildTimeoutResult still sets push-not-landed rejection and builds timeout payload; when kind is not divergent-prefix no early return is triggered, preserving wait-to-deadline behaviour. The adapter default branch (src/adapters/...:139-156) preserves the generic two-cause message for non-divergent-prefix mismatches.

Adoption sweep

Symbol Kind Consumers found Classification Notes
packages/domain/src/session/commands/pr-wait-for-review-subcommand.classifyHeadShaMismatch function packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:16 — imported and exercised directly, packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1149 — used indirectly via local mismatchKind() inside the poll loop Adopted Production path calls it through the local mismatchKind closure; tests import the function directly for unit coverage.

Documentation impact

  • no-update-needed — The change adds an internal classification and branches the adapter’s rendered message accordingly. No new CLI flags or parameters were introduced, and the only user-facing prose touched is in formatTimeoutMessage’s output, which is exercised by existing adapter tests. The spec’s own Scope section noted no docs in docs/ assert the old behavior; this PR does not invalidate published documentation.

@edobry
edobry merged commit 8177d39 into main Sep 4, 2026
20 checks passed
@edobry
edobry deleted the task/mt-4995 branch September 4, 2026 22:48

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Re-verified the prior BLOCKING concern: the push-lag tests now include the classification: "push-pending" field as permitted by the amended SC5, and the classification mechanism is correctly wired end-to-end. The early-return path on divergent-prefix routes through finalizeTimeout as described, and the timeout payload’s classification is non-nullable and gated consistently. Adapter messaging branches as required and tests cover both branches. I found no new critical defects introduced by these changes. Prior non-blocking suggestion about documenting finalizeTimeout’s pre-deadline use remains optional and unaddressed, but is not merge-blocking. Verdict: APPROVE.

Spec verification

Criterion Status Evidence
SC1 — mismatch is classified, not just reported. Met packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:251-336 adds export type HeadShaMismatchKind and export function classifyHeadShaMismatch(...) returning "push-pending" | "divergent-prefix" | null; payload expectedHeadShaUnreached now includes non-nullable classification at :1121-1149 (right-side).
SC2 — the divergent-prefix case does not wait out the timeout. Met packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1326-1350 (right-side) early-returns via finalizeTimeout() when mismatchKind() === "divergent-prefix", with explanatory log, preventing a full-timeout wait.
SC3 — the message names the likely cause and the remedy Met src/adapters/shared/commands/session/pr-wait-for-review-command.ts:112-156 (right-side) branches on classification; for divergent-prefix it replaces the generic two-cause text with specific cause/remedy: do not pad to 40 chars; pass commitHash verbatim; cites mt#4039, mt#4046. Associated test at src/adapters/shared/commands/session/pr-wait-for-review-command.test.ts:239-281 (right-side).
SC4 — the discriminator's false-positive rate is argued, not assumed. Met packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:289-325 carries an in-code rationale (16^7 = 268,435,456) and explains threshold choice; tests pin the threshold at packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:77-95 (right-side).
SC5 — push-pending BEHAVIOUR is unchanged. (Amended: exact-match assertions may be updated to include classification.) Met packages/domain/src/session/commands/pr-wait-for-review-push-lag.test.ts:174-180 and :238-242 (right-side) updated to include classification: "push-pending" while preserving poll-to-deadline assertions; domain timeout path still constructed via buildTimeoutResult when kind !== "divergent-prefix" at packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1118-1160, 1353-1400 (right-side).

Adoption sweep

Symbol Kind Consumers found Classification Notes
packages/domain/src/session/commands/pr-wait-for-review-subcommand.classifyHeadShaMismatch function packages/domain/src/session/commands/pr-wait-for-review-subcommand.ts:1135 — used indirectly via local mismatchKind() in the poll loop, packages/domain/src/session/commands/pr-wait-for-review-sha-classification.test.ts:16 — imported and unit-tested directly Adopted Production path calls it via the local closure; tests import the export for focused coverage.

Documentation impact

  • no-update-needed — No new CLI flags or parameters were added; only the timeout payload gained an internal classification field and the adapter’s formatTimeoutMessage renders a more specific message for one case. The spec’s Scope section already noted no docs in docs/ assert the previous behavior. I spot-checked the in-repo user-surface that mirrors help text (src/adapters/shared/commands/session/session-parameters.ts and src/generated/completion-manifest.json) and both were updated in this PR to describe the new classification behavior, keeping user-facing descriptions accurate.

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

Labels

authorship/co-authored Co-authored by human and AI agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant