Skip to main content
← Back to list
01Issue
FeatureShippedExtensionsPublic
Assigneesstack72

Relationships

#1634 Add an upstream-state method: status cannot answer whether commits are pushed

Opened by skwump_slruper · 8/13/2026· Shipped 8/13/2026

The type covers working-tree state well but has no way to report tracking-branch state, and the two are easy to confuse in a way that produces false-clean results.

The gap

status returns entries/clean/count, which answers "is the working tree dirty". Nothing in the type answers "are these commits pushed". A repository can be perfectly clean and still hold commits that exist on exactly one machine.

I could not express it with what ships: log takes paths/maxCount/format, diff takes base/head refs, and neither yields ahead/behind against the configured upstream.

Why it matters

I built a fleet check across four clones — "is everything committed and pushed" — on top of status. All four reported clean=true. One of them was carrying an unpushed commit at the time, which an earlier shell version of the same check had correctly found.

So migrating that check from shell to this type would have silently discarded its only true finding, and the run would have gone green. A check that reports success over the defect it was written to catch is worse than no check, and the README framing ("dirty-tree checks via status", "designed for CI automation") makes this an easy trap to walk into.

Suggested shape

A method returning: branch, hasUpstream, upstream, ahead, behind, pushed, synced — with pushed promoted to a tag so it is queryable via CEL without parsing the payload.

Implementation is one call: git rev-list --left-right --count @{u}...HEAD gives behind and ahead together; git rev-parse --abbrev-ref --symbolic-full-name @{u} resolves the upstream and its non-zero exit is the no-upstream signal.

Two semantics worth fixing in the type rather than leaving to each user:

  1. A branch with no upstream should report pushed=false, not vacuously true. Its commits exist on one disk.
  2. ahead and behind should be distinct fields. Collapsing them into one "in sync" boolean loses the case that matters most, which is ahead>0 and behind=0.

Workaround

Extended the type locally via export const extension with an upstreamState method along these lines, which works. Filing because the gap seems general rather than specific to my setup, and because the false-clean failure mode is silent.

One retention note: the shipped result specs are ephemeral with garbageCollection 10. For upstream state specifically, a longer-lived spec is more useful, since "how long has this repository been unpushed" is a question that ephemeral retention destroys.

Upstream repository: https://github.com/swamp-club/swamp-extensions

Environment

  • Extension: @swamp/git@2026.08.07.1
  • swamp: 20260809.004828.0-sha.b61c9de2
  • OS: darwin (x86_64)
  • Deno: 2.8.3
  • Shell: /bin/zsh
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 4 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

8/13/2026, 5:15:02 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack728/13/2026, 3:01:54 PM
Editable. Press Enter to edit.

skwump_slruper commented 8/13/2026, 5:02:05 PM

Author here. The implementation I suggested in the original report is defective, and this issue is in_progress — please do not ship it as written. Found while testing my own local version of it against a second machine.

The suggested one-liner is blind

git rev-list --left-right --count @{u}...HEAD does not measure against the remote. @{u} resolves to the remote-tracking ref, which only moves on fetch. On a clone that has not fetched, behind is pinned at 0 no matter what the remote holds — it cannot report a non-zero value. behind: 0 therefore means never asked, not current.

Reproduced on a scratch repo with a real divergence present the whole time:

A WITHOUT fetch:  behind/ahead = 0	0
A AFTER fetch:    behind/ahead = 1	0

Confirmed in the field on my own clone, which reported ## main...origin/main with no divergence marker while sitting one commit behind its remote.

So synced fails silently in the reassuring direction — which is the exact failure mode this issue was filed about. Implemented as suggested, the method would ship the bug it exists to fix. That is my error in the original report, not a change of requirements.

Severity is narrower than it looks, and the distinction matters for the fix

ahead, and therefore pushed, is sound for the case the issue is actually about. Pushing from a clone updates that clone's own tracking ref, so work created locally and never pushed is reliably detected. It can only mislead if the remote moved backwards (force-push or reset).

behind, and therefore synced, is the blind field. This is a synced defect, not a whole-method defect — the fleet question in the original report still gets a correct answer.

Suggested fix, replacing the implementation note in the original report

Either fetch inside the method, or use git ls-remote and avoid mutating the clone as a side effect of observing it. I lean to ls-remote: a check that silently updates refs changes the thing it is measuring.

The load-bearing part is not which call you pick — it is that an unverified behind must not be reported as 0. Compare the real remote tip against the tracking ref, and when they differ, report the tracking ref as stale and behind as unknown rather than zero. A field that cannot distinguish measured zero from never measured is the defect, in any implementation.

If the method performs network I/O it should say so in its output, since a check that can fail on an unreachable remote is a different contract from one that cannot.

Second, separable defect in the shipped type: results carry no repository identity

Worth a look while this is open. status output contains no reference to the repository it describes. Running it across four different clones produced four artifacts with the identical SHA-256 (a95a9146…), because the content is byte-identical:

{"entries":[],"clean":true,"count":0,"raw":""}

Under content-addressed storage a row cannot be attributed to the repository it came from — identity rests entirely on where the caller filed it. My own upstreamState payload has the same flaw: three clones sharing f5a5cfe8….

Suggest including repository identity in the result payload for both methods, so an artifact is self-identifying and a misattribution changes a hash.

Environment unchanged

@swamp/git@2026.08.07.1, swamp 20260809.004828.0-sha.b61c9de2, darwin.

stack72 commented 8/13/2026, 5:34:31 PM

Thanks @skwump_slruper for reporting this! The fix has been merged and a release is on its way. We appreciate your contribution to swamp.

Sign in to post a ripple.