Skip to content

Make entire why deterministic across clones and surface actionable checkpoint-metadata miss reasons#1

Open
karthik-rameshkumar with Copilot wants to merge 3 commits into
mainfrom
copilot/fix-non-deterministic-output
Open

Make entire why deterministic across clones and surface actionable checkpoint-metadata miss reasons#1
karthik-rameshkumar with Copilot wants to merge 3 commits into
mainfrom
copilot/fix-non-deterministic-output

Conversation

Copilot AI commented Jun 28, 2026

Copy link
Copy Markdown

entire why could produce divergent outputs for identical repos when checkpoint metadata was missing, and fetch failures during enrichment were being hidden behind generic “metadata missing” output. This change makes the behavior deterministic and exposes concrete, actionable miss reasons in both text and JSON output.

  • Why-path metadata resolution parity

    • runAttributionWhy now resolves file attribution with fetchOnMiss=true, so file-level entire why <file> follows the same enrichment path as line-level explain behavior.
    • entire blame remains local-only/fast.
  • Structured missing-metadata reason propagation

    • Added MetadataMissingReason to:
      • attributionLine (line output / JSON)
      • attributionCheckpointContext (checkpoint map / JSON)
    • Missing metadata now carries the underlying cause plus a deterministic recovery hint using suggestCheckpointFetchCommand(...), followed by entire checkpoint explain <id> guidance.
  • Fetch-error path no longer swallowed

    • When local read fails and remote refresh also fails, the combined reason is preserved and surfaced, instead of silently degrading to a generic missing marker.
  • Determinism and regression coverage

    • Added focused attribution tests for:
      • missing metadata at line/file level (human + JSON paths),
      • successful local metadata path (MetadataMissingReason == ""),
      • fetch-failure reason surfacing,
      • stable Checkpoints map across successive entire why calls in the same process.
func metadataMissingReason(ctx context.Context, checkpointID string, cause error) string {
	reason := "checkpoint metadata was not found locally"
	if cause != nil {
		reason = fmt.Sprintf("%s (%v)", reason, cause)
	}
	return fmt.Sprintf(
		"%s. Run: %s. Then re-run entire checkpoint explain %s.",
		reason,
		suggestCheckpointFetchCommand(ctx),
		checkpointID,
	)
}

Copilot AI changed the title [WIP] Fix non-deterministic output from entire why command Make entire why deterministic across clones and surface actionable checkpoint-metadata miss reasons Jun 28, 2026

Copilot AI 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.

Pull request overview

This PR makes the entire why attribution path deterministic when checkpoint metadata is missing and surfaces concrete, actionable reasons instead of a generic "metadata missing" marker. It aligns entire why <file> with the line-level enrichment path (remote fetch-on-miss) while keeping entire blame local-only, and threads a new MetadataMissingReason through the line output, checkpoint context, human-readable renderers, and JSON.

Changes:

  • runAttributionWhy now resolves file attribution with fetchOnMiss=true, and a new metadataMissingReason(...) helper builds a recovery hint (git fetch … + entire checkpoint explain <id>).
  • Added MetadataMissingReason to attributionLine and attributionCheckpointContext, propagated through candidate/preferred/render/JSON paths, and preserved the fetch-failure cause instead of swallowing it.
  • Added focused tests covering missing-metadata reasons (line/file, human/JSON), the successful local path, fetch-failure surfacing, and checkpoint-map stability across successive calls.
Show a summary per file
File Description
cmd/entire/cli/attribution.go Adds MetadataMissingReason fields/helper, switches why to fetch-on-miss, preserves fetch-error cause, and renders the reason in text/JSON.
cmd/entire/cli/attribution_test.go Adds tests for missing-metadata reasons, local-metadata path, fetch-failure surfacing, and checkpoint-map determinism.

Notable review findings:

  • readCheckpointContext uses an else after a return, which revive (superfluous-else/indent-error-flow, enabled by default) will flag and fail mise run lint.
  • TestAttributionResolverMissingMetadataIncludesReason runs with fetchOnMiss=true without an isolated repo, so it opens the real checkout and performs a real git fetch, violating test-isolation conventions and introducing network flakiness.
  • With resolveFileAttribution(fetchOnMiss=true), the subsequent enrichAttributionLineWithFetch on the why <file>:<line> path becomes a redundant second remote fetch in the miss case.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Medium

Comment thread cmd/entire/cli/attribution.go Outdated
Comment on lines +396 to +403
resolver := &attributionResolver{
ctx: context.Background(),
store: stubReader,
fetchOnMiss: true,
checkpointCache: make(map[string]attributionCheckpointContext),
}

ctx := resolver.readCheckpointContext(cpID, "auth.py")
Comment thread cmd/entire/cli/attribution.go Outdated
Comment on lines 248 to 252
if selected.MetadataMissing && selected.CheckpointID != "" {
if err := enrichAttributionLineWithFetch(ctx, result.File, selected, result.Checkpoints); err != nil {
// Remote metadata enrichment is best-effort; the trailer-level
// explanation is still useful and should remain available.
selected.MetadataMissing = true
selected.MetadataMissingReason = metadataMissingReason(ctx, selected.CheckpointID, err)
}
}
@blackgirlbytes

Copy link
Copy Markdown

do you mean to have this PR opened in the https://github.com/entireio/cli repository

suhaanthayyil added a commit to suhaanthayyil/cli that referenced this pull request Jun 28, 2026
…sing; refine session-fallback caveat

Addresses review feedback on entireio#1535.

Karthik's report (community Discord + karthik-rameshkumar#1): `entire why`
on a line whose checkpoint metadata is missing printed
"Full context: entire checkpoint explain <id>", but running that command fails
identically ("no checkpoint or commit found matching <id>") because the same
remote fetch `why` attempts has already failed. The hint pointed at a command
that immediately errors.

- Resolve metadata-missing during attribution (resolveFileAttribution with
  fetchOnMiss) and record a MetadataMissingReason that states the cause and the
  actionable remedy: `git fetch <remote> entire/checkpoints/v1:...`, then re-run
  `entire checkpoint explain <id>`. Surfaced in the line and file `why` views
  and in JSON. Also drops the separate post-resolution enrich step, which left
  the checkpoint map non-deterministic across calls. (Mechanism and tests
  adapted from Karthik's PR.)
- Suppress the bare "Full context: entire checkpoint explain <id>" hint when
  metadata is missing: it would fail the same way, and the reason line above now
  gives the fetch-then-explain sequence.
- Refine the session-fallback caveat (Soph's review): flag the multi-session
  fallback whose chosen session has empty FilesTouched (one of several sessions,
  no path evidence) via `sessionsRead > 1 || len(FilesTouched) > 0`, while still
  suppressing the single-session empty-paths false positive. Tests for both.

Co-authored-by: karthik-rameshkumar <6728802+karthik-rameshkumar@users.noreply.github.com>
@suhaanthayyil

Copy link
Copy Markdown

Thanks for this @karthik-rameshkumar — solid diagnosis and a better fix than just hiding the hint. I've folded your MetadataMissingReason mechanism (and the determinism cleanup + your tests) into entireio#1535, which was already in review for the related why/blame prompt + line-syntax changes, so the whole blame/why surface lands in one place. You're credited as co-author on the commit (entireio/cli@dd47f15).

One small addition on top of your change: I also suppress the bare Full context: entire checkpoint explain <id> line when metadata is missing, since your reason message already gives the fetch-then-explain sequence and the bare command would still fail on first run. Wanted to flag it here so the disposition of this PR is clear — thank you for the report and the fix!

suhaanthayyil added a commit to suhaanthayyil/cli that referenced this pull request Jun 29, 2026
…sing; refine session-fallback caveat

Addresses review feedback on entireio#1535.

Karthik's report (community Discord + karthik-rameshkumar#1): `entire why`
on a line whose checkpoint metadata is missing printed
"Full context: entire checkpoint explain <id>", but running that command fails
identically ("no checkpoint or commit found matching <id>") because the same
remote fetch `why` attempts has already failed. The hint pointed at a command
that immediately errors.

- Resolve metadata-missing during attribution (resolveFileAttribution with
  fetchOnMiss) and record a MetadataMissingReason that states the cause and the
  actionable remedy: `git fetch <remote> entire/checkpoints/v1:...`, then re-run
  `entire checkpoint explain <id>`. Surfaced in the line and file `why` views
  and in JSON. Also drops the separate post-resolution enrich step, which left
  the checkpoint map non-deterministic across calls. (Mechanism and tests
  adapted from Karthik's PR.)
- Suppress the bare "Full context: entire checkpoint explain <id>" hint when
  metadata is missing: it would fail the same way, and the reason line above now
  gives the fetch-then-explain sequence.
- Refine the session-fallback caveat (Soph's review): flag the multi-session
  fallback whose chosen session has empty FilesTouched (one of several sessions,
  no path evidence) via `sessionsRead > 1 || len(FilesTouched) > 0`, while still
  suppressing the single-session empty-paths false positive. Tests for both.

Co-authored-by: karthik-rameshkumar <6728802+karthik-rameshkumar@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants