Skip to content

gr verbs report success bound to control flow rather than observed destination state — eight open instances, plus the missing one in gr push #886

Description

@laynepenney

Eight open issues in this repo describe the same defect in eight different verbs. This one proposes treating it as a single architectural defect in how gr reports outcomes, and adds the member that is missing and most consequential: gr push.

The existing instances

Issue Verb Reports success while
#804 gr sync --repo <unknown> matching zero repos
#836 gr branch --repo <unmatched> creating nothing (exits 0)
#839 gr branch --repo <unmatched> "All repos now on branch: X" having moved zero
#814 gr rebase <branch> targeting the stale LOCAL ref, no-op
#768 gr rebase --abort leaving the repo in state=rebasing
#873 gr sync gripspace resolution degraded to a warning
#883 link --apply composing content that is behind upstream
#809 gh project item-add (adjacent tooling) exiting 0 on a silent no-op

Filed separately across three weeks, each by whoever hit it. Read together they are not eight bugs.

The missing member: gr push

Two measurements, two different symptoms, same day.

Symptom A — reported success, remote did not move. A push was reported as successful; the remote ref stayed at its prior SHA. Caught only because the author independently ran git ls-remote over HTTPS afterward, then re-pushed over HTTPS, which landed.

Symptom B — non-zero failures, zero exit code. Measured on a separate run:

⚠ 1 pushed, 1 failed, 17 skipped

  ✗ <repo>: Error: Operation failed: fatal: could not read Username for '<host>': Device not configured

$ echo $?
0

The command printed its own failure and exited 0. Any wrapper, script, or agent gating on the exit code reads that as success.

Why push is the member that matters most

Every other instance in the table wastes time. This one loses work. A push that reports success without moving the remote produces a local branch its author believes is published — so it is not re-pushed, not treated as at-risk, and not caught by any sweep that trusts the tool. The failure is silent at exactly the moment the safety net is supposed to engage.

It also defeats the standard remedy. The natural verification is to read back the remote-tracking ref, and remote-tracking refs are a local cache — they can be stale for reasons unrelated to the push. So a post-push check against origin/<branch> can agree with a push that never happened, and both instruments are wrong in the same direction.

The shape underneath

Every instance is the same substitution: an outcome signal bound to something adjacent to the outcome. The verb reports that it ran, that it iterated, that it encountered no exception in its own frame — never that the intended state change is now true at the destination.

That is why fixing them one at a time has not converged. Eight fixes in eight verbs leave the ninth verb free to make the same substitution, because nothing in the design says what a success claim is required to be about.

Proposed contract

A gr verb's success claim binds to observed destination state, not to internal control flow.

  1. Match count is part of the result. Any verb taking a selector reports how many repos it matched. Zero matched is never silent, and never success. (gr sync --repo <unknown-name> reports success after matching zero repos #804, gr branch reports success and exits 0 when --repo matches no repos, creating nothing #836, gr branch --repo <unmatched> reports "All repos now on branch: X" having moved zero repos #839)
  2. Mutating verbs re-read the thing they changed and report the observed value — remote SHA for push, current branch for branch, repo state for rebase --abort. (gr rebase --abort returns success but leaves repo in state=rebasing (cannot clear its own rebase metadata) #768, gr rebase <branch> targets the stale LOCAL ref and reports success on a no-op #814)
  3. The exit code is the disjunction of per-repo results. Any repo failing makes the process exit non-zero. Printing a failure and exiting 0 is the defect in its purest form. (Symptom B)
  4. Degradations are failures unless explicitly opted into. A resolution that falls back to a warning does not report success. (gripspace resolution failure degrades to a warning while gr sync reports success #873, gitgrip link --apply composes from whatever a gripspace already has on disk and reports full success even when that content is behind upstream #883)
  5. Remote verification does not use remote-tracking refs. push confirms against the remote itself, because the cache can agree with a push that did not happen.

Suggested disposition

Close the eight as children of this one if the contract is adopted, or keep them open and land the contract incrementally with each closing under it. Either is fine. What does not work is a ninth instance issue.

Cross-cutting, so the natural home is the verb-result layer rather than any single command.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions