Skip to content

Commit b56841c

Browse files
committed
ROADMAP ultraworkers#125: git_state 'clean' emitted for non-git directories; GitWorkspaceSummary default all-zeros → is_clean() → 'clean' even when in_git_repo: false; contradictory doctor fields
Dogfooded 2026-04-18 on main HEAD debbcbe from /tmp/cdBB2. Non-git directory: $ mkdir /tmp/cdBB2 && cd /tmp/cdBB2 # NO git init $ claw --output-format json status | jq .workspace.git_state 'clean' # should be null — not in a git repo $ claw --output-format json doctor | jq '.checks[] | select(.name=="workspace") | {in_git_repo, git_state}' {"in_git_repo": false, "git_state": "clean"} # CONTRADICTORY: not in git BUT git is 'clean' Trace: main.rs:2550-2554 parse_git_workspace_summary: let Some(status) = status else { return summary; // all-zero default when no git }; All-zero GitWorkspaceSummary → is_clean() (changed_files==0) → true → headline() = 'clean' main.rs:4950 status JSON: git_summary.headline() for git_state main.rs:1856 doctor workspace: same headline() for git_state Fix shape (~25 lines): - Return Option<GitWorkspaceSummary> when status is None - headline() returns Option<String>: None when no git - Status JSON: git_state: null when not in git - Doctor: omit git_state when in_git_repo: false, or set null - Optional: claw init skip .gitignore in non-git dirs - Regression: non-git → null, git clean → 'clean', detached HEAD → 'clean' + 'detached HEAD' Joins Truth-audit — 'clean' is a lie for non-git dirs. Adjacent to ultraworkers#89 (claw blind to mid-rebase) — same field, different missing state. Joins ultraworkers#100 (status/doctor JSON gaps) — another field whose value doesn't reflect reality. Natural bundle: ultraworkers#89 + ultraworkers#100 + ultraworkers#125 — git-state-completeness triple: rebase/merge invisible (ultraworkers#89) + stale-base unplumbed (ultraworkers#100) + non-git 'clean' lie (ultraworkers#125). Complete git_state field failure coverage. Filed in response to Clawhip pinpoint nudge 1495016073085583442 in #clawcode-building-in-public.
1 parent debbcbe commit b56841c

1 file changed

Lines changed: 68 additions & 0 deletions

File tree

ROADMAP.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4482,3 +4482,71 @@ ear], /color [scheme], /effort [low|medium|high], /fast, /summary, /tag [label],
44824482
**Blocker.** None. Localized across parse + status JSON + doctor check.
44834483

44844484
**Source.** Jobdori dogfood 2026-04-18 against `/tmp/cdAA2` on main HEAD `bb76ec9` in response to Clawhip pinpoint nudge at `1495000973914144819`. Joins **Silent-flag / documented-but-unenforced** (#96–#101, #104, #108, #111, #115, #116, #117, #118, #119, #121, #122, #123) as 17th — `--model` silently accepts garbage with no validation. Joins **Truth-audit / diagnostic-integrity** — status JSON model field has no provenance. Joins **Parallel-entry-point asymmetry** (#91, #101, #104, #105, #108, #114, #117, #122, #123) as 10th — `--model` flag, `.claw.json model`, and the default model are three sources that disagree (#105 adjacent). Natural bundle: **#105 + #124** — model-resolution pair: 4-surface disagreement (#105) + no validation + no provenance (#124). Also **#122 + #124** — unvalidated-flag pair: `--base-commit` accepts anything (#122) + `--model` accepts anything (#124). Same parser pattern. Session tally: ROADMAP #124.
4485+
4486+
125. **`git_state: "clean"` is emitted by both `status` and `doctor` JSON even when `in_git_repo: false` — a non-git directory reports the same sentinel as a git repo with no changes. `GitWorkspaceSummary::default()` returns all-zero fields; `is_clean()` checks `changed_files == 0` → true → `headline() = "clean"`. A claw checking `if git_state == "clean" then proceed` would proceed even in a non-git directory. Doctor correctly surfaces `in_git_repo: false` and `summary: "current directory is not inside a git project"`, but the `git_state` field contradicts this by claiming "clean." Separately, `claw init` creates a `.gitignore` file even in non-git directories — not harmful (ready for future `git init`) but misleading** — dogfooded 2026-04-18 on main HEAD `debbcbe` from `/tmp/cdBB2`.
4487+
4488+
**Concrete repro.**
4489+
```
4490+
$ mkdir /tmp/cdBB2 && cd /tmp/cdBB2
4491+
# NO git init — bare directory
4492+
4493+
$ claw init
4494+
Init
4495+
Project /private/tmp/cdBB2
4496+
.claw/ created
4497+
.claw.json created
4498+
.gitignore created # created in non-git dir
4499+
CLAUDE.md created
4500+
4501+
$ claw --output-format json status | jq '{git_branch: .workspace.git_branch, git_state: .workspace.git_state, project_root: .workspace.project_root}'
4502+
{"git_branch": null, "git_state": "clean", "project_root": null}
4503+
# git_state: "clean" despite NO GIT REPO.
4504+
4505+
$ claw --output-format json doctor | jq '.checks[] | select(.name=="workspace") | {in_git_repo, git_state, status, summary}'
4506+
{"in_git_repo": false, "git_state": "clean", "status": "warn", "summary": "current directory is not inside a git project"}
4507+
# in_git_repo: false BUT git_state: "clean"
4508+
# status: "warn" + summary: "not inside a git project" — CONTRADICTS git_state "clean"
4509+
```
4510+
4511+
**Trace path.**
4512+
- `rust/crates/rusty-claude-cli/src/main.rs:2550-2554` — `parse_git_workspace_summary`:
4513+
```rust
4514+
fn parse_git_workspace_summary(status: Option<&str>) -> GitWorkspaceSummary {
4515+
let mut summary = GitWorkspaceSummary::default();
4516+
let Some(status) = status else {
4517+
return summary; // returns all-zero default when no git
4518+
};
4519+
```
4520+
When `project_context.git_status` is `None` (non-git), returns `GitWorkspaceSummary { changed_files: 0, staged_files: 0, unstaged_files: 0, ... }`.
4521+
- `rust/crates/rusty-claude-cli/src/main.rs:2348-2355` — `GitWorkspaceSummary::headline`:
4522+
```rust
4523+
fn headline(self) -> String {
4524+
if self.is_clean() {
4525+
"clean".to_string()
4526+
} else { ... }
4527+
}
4528+
```
4529+
`is_clean()` = `changed_files == 0` → true for all-zero default → returns "clean" even when there's no git.
4530+
- `rust/crates/rusty-claude-cli/src/main.rs:4950` — status JSON builder uses `context.git_summary.headline()` for the `git_state` field.
4531+
- `rust/crates/rusty-claude-cli/src/main.rs:1856` — doctor workspace check uses the same `headline()` for the `git_state` field, alongside the separate `in_git_repo: false` field.
4532+
4533+
**Why this is specifically a clawability gap.**
4534+
1. *False positive "clean" on non-git directories.* A claw preflighting with `git_state == "clean" && project_root != null` would work. But a claw checking ONLY `git_state == "clean"` (the simpler, more obvious check) would proceed in non-git directories. The `null` project_root is the real guard, but git_state misleads.
4535+
2. *Contradictory fields in doctor.* `in_git_repo: false` + `git_state: "clean"` in the same check. A claw reading one field gets "not in git"; reading the other gets "git is clean." The two fields should be consistent or `git_state` should be `null`/absent when `in_git_repo` is false.
4536+
3. *Joins truth-audit.* The "clean" sentinel is a truth claim about git state. When there's no git, the claim is vacuously true at best, actively misleading at worst.
4537+
4. *Adjacent to #89 (claw blind to mid-rebase/merge).* #89 said git_state doesn't capture rebase/merge/cherry-pick. **#125** says git_state also doesn't capture "not in git" — another missing state.
4538+
5. *Minor: `claw init` creates `.gitignore` without git.* Not harmful but joins the pattern of init producing artifacts for absent subsystems (`.gitignore` without git, `.claw.json` with `dontAsk` per #115).
4539+
4540+
**Fix shape — null `git_state` when not in git repo.**
4541+
1. *Return `None` from `parse_git_workspace_summary` when status is `None`.* Change return type to `Option<GitWorkspaceSummary>`. ~10 lines.
4542+
2. *`headline()` returns `Option<String>`.* `None` when no git, `Some("clean")` / `Some("dirty · ...")` when in git. ~5 lines.
4543+
3. *Status JSON: `git_state: null` when not in git.* Currently always a string. ~3 lines.
4544+
4. *Doctor check: omit `git_state` field entirely when `in_git_repo: false`.* Or set to `null` / `"no-git"`. ~3 lines.
4545+
5. *Optional: `claw init` warns when creating `.gitignore` in non-git directory.* Or: skip `.gitignore` creation when not in git. ~5 lines.
4546+
6. *Regression tests.* (a) Non-git directory → `git_state: null` (not "clean"). (b) Git repo with clean state → `git_state: "clean"`. (c) Detached HEAD → `git_state: "clean"` + `git_branch: "detached HEAD"` (current behavior, already correct).
4547+
4548+
**Acceptance.** `claw --output-format json status` in a non-git directory shows `git_state: null` (not "clean"). Doctor workspace check with `in_git_repo: false` has `git_state: null` (or absent). A claw checking `git_state == "clean"` correctly rejects non-git directories.
4549+
4550+
**Blocker.** None. ~25 lines across two files.
4551+
4552+
**Source.** Jobdori dogfood 2026-04-18 against `/tmp/cdBB2` on main HEAD `debbcbe` in response to Clawhip pinpoint nudge at `1495016073085583442`. Joins **Truth-audit / diagnostic-integrity** — `git_state: "clean"` is a lie for non-git directories. Adjacent to **#89** (claw blind to mid-rebase) — same field, different missing state. Joins **#100** (status/doctor JSON gaps) — another field whose value doesn't reflect reality. Natural bundle: **#89 + #100 + #125** — git-state-completeness triple: rebase/merge invisible (#89) + stale-base unplumbed (#100) + non-git "clean" lie (#125). Complete coverage of git_state field failures. Session tally: ROADMAP #125.

0 commit comments

Comments
 (0)