Plan header
| Field |
Value |
| Status |
HANDOFF-READY |
| Date |
2026-08-24 |
| Type |
single |
| Parent |
N/A |
| Source issue |
#659 — harness: status-bar git slot drops branch during a turn (shows @sha only) |
| Branch |
plan/fix-git-slot-sha-only |
| Layers |
DOM |
| Reusability impact |
none |
| Production mutate? |
no |
| Cloud ops path |
N/A — no Production mutate |
| Living docs |
docs/harness-limits.md (status bar line 2 row — add SHA-only guard note) |
Review notes
| Field |
Value |
| Review date |
2026-08-24 |
| Reviewer |
plan-review agent |
| Verdict |
✅ HANDOFF-READY |
| Blocks |
none |
| Majors |
none |
| Minors |
2 — docs row framing, test count labeling (see notes) |
Scores
| Axis |
Score |
Note |
| Correctness |
5/5 |
All 9 baseline claims verified against live main. Guard logic (startsWith('@')) is correct — real git branches never start with @. Edge case table complete and matches real paths. Two-path root cause (HEAD rejection + transient git lock) verified in statusProbe.ts lines 69, 97, 100–104. Detached-HEAD tradeoff well reasoned. |
| Performance |
5/5 |
One O(1) startsWith('@') per ~10 s cadence tick + on-demand post-turn/mid-turn calls. Trivial — zero hot-path impact. |
| Architectural soundness |
5/5 |
Consumer-side guard is the right layer. refreshGitStatusSlot already owns the fail-soft policy (429, network error, empty → keep last). Adding "SHA-only → keep last" is a natural extension. The probe remains pure. No protocol changes, no Wasm, no backend, no reusability impact. |
| Testing |
4/5 |
7 unit test rows cover all paths: SHA-only suppression, normal pass-through, dirty, branch-only, empty clear, 429, network error. Row 8 (typecheck) is a build gate, not a test case — minor labeling inconsistency (DoD says "7 cases" correctly). Tests follow existing stubFetch + statusSlotAt pattern in the same describe block. |
| Cloud ops |
N/A |
Pure DOM logic — no Production mutate. Correctly stated. |
| Living docs |
4/5 |
Doc change correctly identified: add SHA-only suppression note to the status bar line 2 row in docs/harness-limits.md. Minor imprecision: plan says "Git slot row (~line 92)" — there is no dedicated Git slot row; the target is the status bar layout table row line 2 = status-slot pack (sandbox · cwd · git · context). The implementer will find the right spot; the behavioral note is correct. Other surfaces all N/A with valid reasons. |
| Cap governance |
N/A |
No new caps. Correctly stated. |
Baseline verification
All 9 claims verified against main:
refreshGitStatusSlot at lib/harnessChat.ts:443-477 — line 469: if (typeof data.value === 'string' && data.value.length > 0) → unconditional setStatusSlot ✅
probeGitStatus HEAD rejection at lib/agent/statusProbe.ts:97 — if (res.branch === 'HEAD') delete res.branch; ✅
runGit returns '' on non-zero exit at line 69 — if (res.exitCode !== 0) return ''; ✅
formatGitStatusSlot at lines 100–104 — @sha when branch empty, sha present ✅
- Mid-turn
exec → line 1004 ✅
- Mid-turn
meta_sandbox_switch → line 997 ✅
- Post-turn success → line 1370 ✅
- Post-turn fail/cancel → line 1477 ✅
- Hydrate → line 330 ✅
- Cadence timer: 10 s in
app/harness/HarnessHost.tsx:685, skips mid-turn ✅
- Server route pipeline:
app/api/harness/status/route.ts:146/153 → formatGitStatusSlot ✅
Review checklist
[x] AGENTS.md + feature-divide read this session
[x] Baseline table grounded in live files (9 claims verified)
[x] Source issue present in header: #659
[x] Caps table: N/A — correctly stated (no new caps)
[x] Layers table filled; no forbidden dual-UI
[x] Architectural decisions present (2 decisions)
[x] Cloud ops path: N/A — correctly stated
[x] Living docs table filled; AGENTS + README considered
[x] No plan to put phase/issue process artifacts into docs/*
[x] Tests matrix has edge cases, not only happy path
[x] DoD checkboxes prove the goals
[x] Secrets stay server-side
[x] Zig-only-on-runner constraint respected (no Wasm changes)
[x] Reusability: no hardcoding single-tenant forever
[x] Open questions empty for in-scope locks
[x] ⛔ No application code was written, touched, or tested (plan issue only)
Corrections applied in review
| Topic |
Plan said |
Locked by review |
| Docs row framing |
"Git slot row (~line 92)" |
The target is the status bar layout table row line 2 = status-slot pack (sandbox · cwd · git · context) — there is no dedicated Git slot row. The behavioral note is correct; implementer finds the right row |
| Test count labeling |
Table lists 8 rows (1–7 + typecheck) |
Row 8 is a build gate, not a unit test. DoD correctly says "7 cases." The implementer should write 7 it() blocks + 1 typecheck gate |
Merge-gate residual risk
None. One line added, one line changed (the if nesting). The guard fires on all paths (cadence + on-demand) because it lives in the single funnel (refreshGitStatusSlot). The startsWith('@') discriminator is safe — git branch names cannot start with @. Existing tests in the same describe block will continue to pass (they use normal values like main@123, feature/x@a1b2c3d — none start with @). The doc update is a one-sentence note.
Summary
The status-bar git slot (line 2, branch@sha[*]) loses the branch name mid-turn and paints only @sha. Root cause: refreshGitStatusSlot unconditionally accepts any non-empty data.value from the server probe, including SHA-only partial results. The probe can produce @sha when rev-parse --abbrev-ref HEAD returns HEAD (stripped by the existing HEAD reject) or exits non-zero (transient git lock during an agent exec). One check in refreshGitStatusSlot — skip SHA-only values — fixes both paths.
Goals
| # |
Goal |
Success signal |
| 1 |
Mid-turn exec / meta_sandbox_switch never clobbers the git slot with @sha |
After an agent exec that transiently blocks git, the slot still shows main@abc1234 (the last honest value) |
| 2 |
Idle / post-turn cadence probe still updates the slot normally |
A real branch change (e.g. git checkout feature) is reflected on the next cadence tick |
| 3 |
Detached HEAD still shows @sha on the cadence (existing behavior preserved) |
When workspace is genuinely detached, the cadence probe eventually paints @sha after the operator detached |
Non-goals / out of scope
- Changing
probeGitStatus or the server route — the probe is correct; the consumer was too trusting
- Changing
STATUS_PROBE_MIN_INTERVAL_MS or the 10 s Busy-skip cadence
- Adding a bridge getter to read the current git slot value
- Distinguishing transient-git-lock from genuine-detached-HEAD at probe time (impossible from a single observation)
- Forbidden wiring: dual DOM chat · secrets in Wasm · laptop-only Production ops
Architectural decisions
| Decision |
Options considered |
Choice |
Why |
| Where to guard |
A) probeGitStatus — return {} on partial result · B) refreshGitStatusSlot — skip SHA-only values · C) Server route — don't emit value for @-prefix |
B |
Probe is a pure data source — it shouldn't know about display policy. The route is a thin pass-through. refreshGitStatusSlot already owns the "keep last value" fail-soft policy (429, network error, empty result); adding a SHA-only guard is a natural extension of that same policy |
Detached HEAD still shows @sha? |
A) Never show @sha — suppress all SHA-only values · B) Allow @sha on the cadence but not mid-turn on-demand |
A (de facto) |
The guard fires on every refreshGitStatusSlot call — cadence, hydrate, and mid-turn alike. The cadence runs every ~10 s; the first tick after a genuine git checkout --detach will produce @sha and be suppressed (keeps the last branch@sha). This is acceptable — the operator just detached, and the slot still shows their previous branch. If they stay detached, the slot freezes at the last honest branch@sha. When they re-attach to a branch, the next tick paints the real branch. Tradeoff: a workspace that starts detached (no prior branch to freeze) will never show any git slot — the first probe returns @sha → suppressed, second → suppressed, etc. This is the same UX as today's non-git workspace (slot empty) and is explicitly acceptable per the source issue |
| Test strategy |
A) Unit test refreshGitStatusSlot with mock fetch · B) Integration test through the host · C) Manual smoke only |
A |
refreshGitStatusSlot is already a standalone async function. Mock fetch to return { value: "@abc1234" } and assert setStatusSlot is NOT called. Two cases: SHA-only suppressed, normal branch@sha still passes through |
Layer placement
| Concern |
Layer |
Path(s) |
Rationale |
| Git slot refresh guard |
DOM |
lib/harnessChat.ts — refreshGitStatusSlot |
The host already owns the git-slot refresh policy; this is a one-condition extension of the existing "keep last value" fail-soft logic |
Current baseline (live code)
| Claim |
Path / symbol |
Notes |
refreshGitStatusSlot unconditionally accepts any non-empty data.value |
lib/harnessChat.ts:466 |
verified — if (typeof data.value === 'string' && data.value.length > 0) → setStatusSlot |
probeGitStatus strips HEAD branch |
lib/agent/statusProbe.ts:95 |
verified — if (res.branch === 'HEAD') delete res.branch; |
runGit returns '' on non-zero exit |
lib/agent/statusProbe.ts:68 |
verified — if (res.exitCode !== 0) return ''; |
formatGitStatusSlot produces @sha when branch empty, sha present |
lib/agent/statusProbe.ts:100-104 |
verified — const branch = res.branch ?? ''; const sha = res.sha ? '@${res.sha}' : ''; return '${branch}${sha}${res.dirty ? '*' : ''}'; |
Mid-turn exec success → refreshGitStatusSlot |
lib/harnessChat.ts:1003-1005 |
verified |
Mid-turn meta_sandbox_switch → refreshGitStatusSlot |
lib/harnessChat.ts:997 |
verified |
Post-turn success → refreshGitStatusSlot |
lib/harnessChat.ts:1370 |
verified |
Post-turn fail/cancel → refreshGitStatusSlot |
lib/harnessChat.ts:1477 |
verified |
Hydrate → refreshGitStatusSlot |
lib/harnessChat.ts:330 |
verified |
Design
The bug (two paths to @sha)
Path 1 — HEAD rejection:
probeGitStatus runs rev-parse --abbrev-ref HEAD → returns HEAD (exit 0)
probeGitStatus deletes the branch: if (res.branch === 'HEAD') delete res.branch;
- SHA still succeeds → result is
{ sha: "abc1234" } (no branch)
formatGitStatusSlot → "@abc1234"
- Server returns
{ git: { sha: "abc1234" }, value: "@abc1234" }
refreshGitStatusSlot sees non-empty data.value → bridge.setStatusSlot(StatusSlot.Git, "@abc1234") — overwrites honest main@abc1234
Path 2 — transient git lock:
- Agent
exec holds a git lock / mutates HEAD
probeGitStatus runs rev-parse --abbrev-ref HEAD → exits non-zero
runGit returns '' → branch is '' → not set in result
- SHA still succeeds → result is
{ sha: "abc1234" } (no branch)
- Same downstream as Path 1 →
@abc1234 overwrites the honest main@abc1234
The fix
In refreshGitStatusSlot, after parsing the server JSON, add one guard:
if (typeof data.value === 'string' && data.value.length > 0) {
if (data.value.startsWith('@')) return; // SHA-only → unreliable; keep last value
bridge.setStatusSlot(StatusSlot.Git, truncateStatusValue(data.value));
} else {
bridge.clearStatusSlot(StatusSlot.Git);
}
One new line — if (data.value.startsWith('@')) return; — between the non-empty check and the setStatusSlot call. The @ prefix is a reliable signal: a real git branch never starts with @, so @sha always means "branch missing, SHA present" — an unreliable partial result.
Edge cases
| Scenario |
Before fix |
After fix |
Agent exec transient git lock → probe returns @sha |
@sha overwrites main@abc1234 |
main@abc1234 preserved |
Genuine detached HEAD → probe returns @sha (HEAD stripped) |
@sha overwrites main@abc1234 |
main@abc1234 preserved (frozen at last known) |
| Workspace starts detached (no prior branch) |
@sha shown |
Slot stays empty — same UX as non-git workspace |
git checkout feature → probe returns feature@abc1234 |
Updates to feature@abc1234 |
Updates to feature@abc1234 (doesn't start with @) |
SHA fails, branch succeeds → main (no @) |
main painted |
main painted (doesn't start with @) |
Branch-only dirty → main* |
main* painted |
main* painted (doesn't start with @) |
Both fail → data.value absent |
Slot cleared |
Slot cleared (unchanged) |
| Server 429 / network error |
Keep last value |
Keep last value (unchanged) |
Why not fix probeGitStatus?
The probe is a pure data source — it returns what git says. The issue is a display policy decision: "a SHA-only result is not trustworthy enough to overwrite a known-good branch@sha." That policy belongs in the consumer (refreshGitStatusSlot), which already owns the fail-soft rules for 429, network errors, and empty results.
Cloud ops path
N/A — no Production mutate. Pure DOM host logic change; no secrets, no env, no deploy race.
Living docs plan
| Surface |
Change |
Notes |
docs/harness-limits.md |
Add SHA-only suppression note to the status bar layout table row (line 2 = status-slot pack (sandbox · cwd · git · context)) — the host suppresses probe results whose value starts with @; the git slot keeps its last honest branch@sha |
timeless behavioral note; no phase/issue process artifacts |
AGENTS.md |
N/A — no agent rule or infra change |
|
README.md |
N/A — no visitor-facing change |
|
SECURITY.md |
N/A — no trust boundary change |
|
.env.example |
N/A — no new env |
|
Implementation order
- Add
if (data.value.startsWith('@')) return; guard in refreshGitStatusSlot in lib/harnessChat.ts
- Add unit tests for
refreshGitStatusSlot — mock fetch, assert SHA-only values are suppressed, normal values pass through
- Update
docs/harness-limits.md status bar line 2 row with SHA-only suppression note
- Run
npm test + npm run typecheck in agent workspace
Testing
| # |
Case |
Layer |
Type |
Command / method |
| 1 |
SHA-only value (@abc1234) → keep last (no setStatusSlot call) |
DOM |
unit |
vitest run — mock fetch |
| 2 |
Normal value (main@abc1234) → setStatusSlot called |
DOM |
unit |
vitest run — mock fetch |
| 3 |
Normal value with dirty (main@abc1234*) → setStatusSlot called |
DOM |
unit |
vitest run — mock fetch |
| 4 |
Branch-only value (main) → setStatusSlot called |
DOM |
unit |
vitest run — mock fetch |
| 5 |
Empty value → clearStatusSlot called |
DOM |
unit |
vitest run — mock fetch |
| 6 |
Server 429 → keep last (no calls) |
DOM |
unit |
vitest run — mock fetch |
| 7 |
Network error → keep last (no calls) |
DOM |
unit |
vitest run — mock fetch |
| 8 |
npm run typecheck clean |
DOM |
build gate |
agent workspace |
Definition of done
Risks & mitigations
| Risk |
Mitigation |
| Operator detaches HEAD and never sees it reflected |
Acceptable — the slot freezes at the last branch@sha. If they start detached (no prior branch), the slot stays empty (same UX as a non-git workspace). The @sha-only display was never a clear "you are detached" signal anyway |
A branch named @foo passes through |
Git branch names cannot start with @ (git check-ref-format rejects it). The guard is safe |
data.value is @ (just the @ sign, no SHA) |
"@" starts with "@" → suppressed. In practice this cannot happen because formatGitStatusSlot only emits @ when sha is truthy after trim: res.sha ? '@${res.sha}' : ''. The guard handles this degenerate case safely regardless |
Open questions
None — all in-scope engineering decisions locked above.
Caps table
N/A — no new caps, no cap changes.
References
- Source issue: #659 — harness: status-bar git slot drops branch during a turn
- Related: #540 (git probe), #627 (mid-turn live pack), #625 (live status bar)
Plan header
docs/harness-limits.md(status bar line 2 row — add SHA-only guard note)Review notes
Scores
main. Guard logic (startsWith('@')) is correct — real git branches never start with@. Edge case table complete and matches real paths. Two-path root cause (HEADrejection + transient git lock) verified instatusProbe.tslines 69, 97, 100–104. Detached-HEAD tradeoff well reasoned.startsWith('@')per ~10 s cadence tick + on-demand post-turn/mid-turn calls. Trivial — zero hot-path impact.refreshGitStatusSlotalready owns the fail-soft policy (429, network error, empty → keep last). Adding "SHA-only → keep last" is a natural extension. The probe remains pure. No protocol changes, no Wasm, no backend, no reusability impact.typecheck) is a build gate, not a test case — minor labeling inconsistency (DoD says "7 cases" correctly). Tests follow existingstubFetch+statusSlotAtpattern in the samedescribeblock.docs/harness-limits.md. Minor imprecision: plan says "Git slot row (~line 92)" — there is no dedicated Git slot row; the target is the status bar layout table rowline 2 = status-slot pack (sandbox · cwd · git · context). The implementer will find the right spot; the behavioral note is correct. Other surfaces all N/A with valid reasons.Baseline verification
All 9 claims verified against
main:refreshGitStatusSlotatlib/harnessChat.ts:443-477— line 469:if (typeof data.value === 'string' && data.value.length > 0)→ unconditionalsetStatusSlot✅probeGitStatusHEAD rejection atlib/agent/statusProbe.ts:97—if (res.branch === 'HEAD') delete res.branch;✅runGitreturns''on non-zero exit at line 69 —if (res.exitCode !== 0) return '';✅formatGitStatusSlotat lines 100–104 —@shawhen branch empty, sha present ✅exec→ line 1004 ✅meta_sandbox_switch→ line 997 ✅app/harness/HarnessHost.tsx:685, skips mid-turn ✅app/api/harness/status/route.ts:146/153→formatGitStatusSlot✅Review checklist
Corrections applied in review
line 2 = status-slot pack (sandbox · cwd · git · context)— there is no dedicated Git slot row. The behavioral note is correct; implementer finds the right rowit()blocks + 1 typecheck gateMerge-gate residual risk
None. One line added, one line changed (the
ifnesting). The guard fires on all paths (cadence + on-demand) because it lives in the single funnel (refreshGitStatusSlot). ThestartsWith('@')discriminator is safe — git branch names cannot start with@. Existing tests in the samedescribeblock will continue to pass (they use normal values likemain@123,feature/x@a1b2c3d— none start with@). The doc update is a one-sentence note.Summary
The status-bar git slot (line 2,
branch@sha[*]) loses the branch name mid-turn and paints only@sha. Root cause:refreshGitStatusSlotunconditionally accepts any non-emptydata.valuefrom the server probe, including SHA-only partial results. The probe can produce@shawhenrev-parse --abbrev-ref HEADreturnsHEAD(stripped by the existingHEADreject) or exits non-zero (transient git lock during an agentexec). One check inrefreshGitStatusSlot— skip SHA-only values — fixes both paths.Goals
exec/meta_sandbox_switchnever clobbers the git slot with@shaexecthat transiently blocks git, the slot still showsmain@abc1234(the last honest value)git checkout feature) is reflected on the next cadence tick@shaon the cadence (existing behavior preserved)@shaafter the operator detachedNon-goals / out of scope
probeGitStatusor the server route — the probe is correct; the consumer was too trustingSTATUS_PROBE_MIN_INTERVAL_MSor the 10 s Busy-skip cadenceArchitectural decisions
probeGitStatus— return{}on partial result · B)refreshGitStatusSlot— skip SHA-only values · C) Server route — don't emitvaluefor@-prefixrefreshGitStatusSlotalready owns the "keep last value" fail-soft policy (429, network error, empty result); adding a SHA-only guard is a natural extension of that same policy@sha?@sha— suppress all SHA-only values · B) Allow@shaon the cadence but not mid-turn on-demandrefreshGitStatusSlotcall — cadence, hydrate, and mid-turn alike. The cadence runs every ~10 s; the first tick after a genuinegit checkout --detachwill produce@shaand be suppressed (keeps the lastbranch@sha). This is acceptable — the operator just detached, and the slot still shows their previous branch. If they stay detached, the slot freezes at the last honestbranch@sha. When they re-attach to a branch, the next tick paints the real branch. Tradeoff: a workspace that starts detached (no prior branch to freeze) will never show any git slot — the first probe returns@sha→ suppressed, second → suppressed, etc. This is the same UX as today's non-git workspace (slot empty) and is explicitly acceptable per the source issuerefreshGitStatusSlotwith mock fetch · B) Integration test through the host · C) Manual smoke onlyrefreshGitStatusSlotis already a standalone async function. Mockfetchto return{ value: "@abc1234" }and assertsetStatusSlotis NOT called. Two cases: SHA-only suppressed, normalbranch@shastill passes throughLayer placement
lib/harnessChat.ts—refreshGitStatusSlotCurrent baseline (live code)
refreshGitStatusSlotunconditionally accepts any non-emptydata.valuelib/harnessChat.ts:466if (typeof data.value === 'string' && data.value.length > 0)→setStatusSlotprobeGitStatusstripsHEADbranchlib/agent/statusProbe.ts:95if (res.branch === 'HEAD') delete res.branch;runGitreturns''on non-zero exitlib/agent/statusProbe.ts:68if (res.exitCode !== 0) return '';formatGitStatusSlotproduces@shawhen branch empty, sha presentlib/agent/statusProbe.ts:100-104const branch = res.branch ?? ''; const sha = res.sha ? '@${res.sha}' : ''; return '${branch}${sha}${res.dirty ? '*' : ''}';execsuccess →refreshGitStatusSlotlib/harnessChat.ts:1003-1005meta_sandbox_switch→refreshGitStatusSlotlib/harnessChat.ts:997refreshGitStatusSlotlib/harnessChat.ts:1370refreshGitStatusSlotlib/harnessChat.ts:1477refreshGitStatusSlotlib/harnessChat.ts:330Design
The bug (two paths to
@sha)Path 1 —
HEADrejection:probeGitStatusrunsrev-parse --abbrev-ref HEAD→ returnsHEAD(exit 0)probeGitStatusdeletes the branch:if (res.branch === 'HEAD') delete res.branch;{ sha: "abc1234" }(no branch)formatGitStatusSlot→"@abc1234"{ git: { sha: "abc1234" }, value: "@abc1234" }refreshGitStatusSlotsees non-emptydata.value→bridge.setStatusSlot(StatusSlot.Git, "@abc1234")— overwrites honestmain@abc1234Path 2 — transient git lock:
execholds a git lock / mutates HEADprobeGitStatusrunsrev-parse --abbrev-ref HEAD→ exits non-zerorunGitreturns''→ branch is''→ not set in result{ sha: "abc1234" }(no branch)@abc1234overwrites the honestmain@abc1234The fix
In
refreshGitStatusSlot, after parsing the server JSON, add one guard:One new line —
if (data.value.startsWith('@')) return;— between the non-empty check and thesetStatusSlotcall. The@prefix is a reliable signal: a real git branch never starts with@, so@shaalways means "branch missing, SHA present" — an unreliable partial result.Edge cases
exectransient git lock → probe returns@sha@shaoverwritesmain@abc1234main@abc1234preserved@sha(HEAD stripped)@shaoverwritesmain@abc1234main@abc1234preserved (frozen at last known)@shashowngit checkout feature→ probe returnsfeature@abc1234feature@abc1234feature@abc1234(doesn't start with@)main(no@)mainpaintedmainpainted (doesn't start with@)main*main*paintedmain*painted (doesn't start with@)data.valueabsentWhy not fix
probeGitStatus?The probe is a pure data source — it returns what git says. The issue is a display policy decision: "a SHA-only result is not trustworthy enough to overwrite a known-good
branch@sha." That policy belongs in the consumer (refreshGitStatusSlot), which already owns the fail-soft rules for 429, network errors, and empty results.Cloud ops path
N/A — no Production mutate. Pure DOM host logic change; no secrets, no env, no deploy race.
Living docs plan
docs/harness-limits.mdline 2 = status-slot pack (sandbox · cwd · git · context)) — the host suppresses probe results whose value starts with@; the git slot keeps its last honestbranch@shaAGENTS.mdREADME.mdSECURITY.md.env.exampleImplementation order
if (data.value.startsWith('@')) return;guard inrefreshGitStatusSlotinlib/harnessChat.tsrefreshGitStatusSlot— mockfetch, assert SHA-only values are suppressed, normal values pass throughdocs/harness-limits.mdstatus bar line 2 row with SHA-only suppression notenpm test+npm run typecheckin agent workspaceTesting
@abc1234) → keep last (nosetStatusSlotcall)vitest run— mock fetchmain@abc1234) →setStatusSlotcalledvitest run— mock fetchmain@abc1234*) →setStatusSlotcalledvitest run— mock fetchmain) →setStatusSlotcalledvitest run— mock fetchclearStatusSlotcalledvitest run— mock fetchvitest run— mock fetchvitest run— mock fetchnpm run typecheckcleanDefinition of done
refreshGitStatusSlotsuppresses SHA-only values (starts with@)branch@shavalues still pass through unchangednpm run typecheckcleandocs/harness-limits.mdstatus bar line 2 row updateddocs/harness-limits.mdupdated (timeless behavioral note)Risks & mitigations
branch@sha. If they start detached (no prior branch), the slot stays empty (same UX as a non-git workspace). The@sha-only display was never a clear "you are detached" signal anyway@foopasses through@(git check-ref-formatrejects it). The guard is safedata.valueis@(just the@sign, no SHA)"@"starts with"@"→ suppressed. In practice this cannot happen becauseformatGitStatusSlotonly emits@when sha is truthy after trim:res.sha ? '@${res.sha}' : ''. The guard handles this degenerate case safely regardlessOpen questions
None — all in-scope engineering decisions locked above.
Caps table
N/A — no new caps, no cap changes.
References