Skip to content

diag: min bug bundles lack project-scope config and closure provenance, causing misattributed diagnoses #1211

Description

@norrietaylor

Motivation

The diagnosis in #1204 correctly reconstructed the failure mechanism (a ~/.claude.json credential mapping tilde-expanded against the guest daemon's HOME=/, EROFS on the read-only rootfs) but misattributed where claude-code entered the failing tasks' package closures: it guessed the applied loadout, when the actual source was the project's default dev profile (follow-up).

The misattribution was structural, not a reasoning error: the bundle carries user-scope config (config/config.toml, user_policy.toml, config/loadouts/*.toml) but nothing project-scope and no record of the resolved package closure. The only claude-code sighting available in the bundle was an unapplied loadout file. Every fact needed for the correct attribution sat in the project directory min bug was run from.

Proposed enhancements

1. Project-scope collector

crates/minimal/src/diag/collect.rs gains a collector that discovers the invoking project via mfile from the bundle's cwd and adds:

  • project/minimal.toml.redacted — through the existing add_redacted_toml path (values redacted; keys, package names, task names, and [defaults] survive, which is what closure attribution needs).
  • project/profiles.json — profiles are Nickel, so redact_toml cannot process them; instead of a Nickel redactor, evaluate them through the decode crate and emit a names-only JSON of profile → resolved package list (inheritance flattened). Fail-closed by construction: no config values leave the machine. A decode failure is recorded as a skip — itself diagnostic.

When no project exists at the cwd, the collector records a skip ("no project at <cwd>") per the manifest's absence-is-explainable contract; the diag portal can then prompt the reporter to re-run from the failing project.

Collectors must not mutate what they read; decode evaluation is read-only, and the existing collect_step! timeout bounds an evaluation that hangs on a broken config.

2. Effective-loadout resolution record

"Applying loadouts: default" was ambiguous in the bundle because nothing showed what default resolves to. Emit config/loadouts-resolved.json: each loadout name that would apply, its source (built-in vs user file path, via the shadowing logic in crates/minimal/src/loadouts.rs), and its package list. For #1204 this would have stated outright that default is the built-in from #1128 with zero packages — eliminating the wrong suspect, not just adding the right one.

3. Mapping provenance in task-exec logs

The task-exec path logs no resolved fs_mappings (only the session path does, post credential-filter), so the ~ → / expansion in #1204 had to be established entirely by code reading. Log the resolved mapping list at exec setup — pre- and post-expansion path plus the contributing package. Guest logs already reach the bundle via the nested daemon bundle, so no collector change is needed. This pairs with #1204's existing recommendation that the EROFS error name the contributing package and the unexpanded path.

Scope note

Items 1–2 amend the diagnostics spec series (docs/specs/10-spec-diagnostics/ has no project-scope collection — unaddressed rather than deliberately excluded) and belong in a new demoable unit. Item 3 is a small daemon logging change independent of the spec.

Items 1 and 2 alone would have converted #1204's closure attribution from "medium confidence, wrong" to "high confidence, correct".

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