You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(session): loadSession extraStart indexed postCPEntries but sliced allMessages — panic on long/cost-record sessions
test(#516): add registration-guard test for hardcoded-path — the detector spent multiple census rounds as unregistered dead code precisely because nothing asserted its registration; TestCheckWriteIntegrity_HardcodedPathRegistered goes through the public checkWriteIntegrity entry (old/new delta with a newly introduced /Users/... path must warn 'macOS home path'), guarding against silent deregistration in future registry refactors. Companion to 0713a6f.
test(#513): add persistent characterization tests for the two probe-verified bugs the issue's temporary probes covered — secondsToDuration clamp (overflow table: negative/zero fallback, 9223372037s negative wrap, 18446744074s positive wrap, 99999999999s all clamped to 86400s) and snapshot TotalLines partial-line exclusion (end-to-end via CommandJobManager: partial chunk visible as preview but TotalLines stays 1, merged chunk increments to 2, since_line=1 polling returns the merged complete line instead of the old permanent []). The Bug3 ctx test was already committed inverted in 8b409dc; wailskit chat.go fixes are covered by the package's existing regression suite (no prior assertion encoded the buggy behavior).
fix(#516): delete 4 dead detectors, register hardcoded-path live — R73 census batch, sa-172 probe-verified (12/13 hypotheses PASS, temp probe files removed). DEAD DELETES (10th census instance of the #328/#330/#499/#503-#510 class): duplicate_code_check.go — token-frequency Jaccard is order-blind: two functions with identical token multisets but opposite execution order report "100% similar / structurally identical bodies" (its own L124-129 concedes the frequency set can be 1.0 while order differs); header comment says min 5 stmts but const duplicateMinStmts = 3. hardcoded_host_check.go — semantic inversion across Go/JS/Python paths: host:'localhost' and 127.0.0.1 flagged as "binding to unintended interfaces" when loopback is the MOST conservative binding and the stated risk is 0.0.0.0. http_timeout_check.go — doubly broken: set-delta keys (default-client-func:http.Get) are non-multiset so delete-2-add-1-new swallows brand-new violations (0 warnings where fresh-file identical content warns twice; #171 class), AND it only inspects composite-literal fields so c := &http.Client{}; c.Timeout = 30*time.Second still warns "created without a Timeout field". loop_perf_check.go — triple-broken: double-counts nested-loop += (same line recorded twice), misses string-typed function params entirely (identifyStringVars has no param case), and stringVars is file-global-by-name so a string s in func A poisons an int s in func B. LIVE REGISTER: hardcoded-path (checkHardcodedPaths) — the only zero-known-FP/FN survivor of the sa-172 batch (already implements the #186/#171 per-instance multiset delta; .go home-path fires, .md stays silent), following the #508 empty-error-body revival precedent. DEPENDENCY DISPOSITION (#509 fate-binding precedent): identifyStringVars/isStringExpr + 3 collect helpers migrated from loop_perf_check.go to string_efficiency_check.go (itself unregistered dead code — must be re-adjudicated together when string_efficiency reaches census); qualifiedCallName stays in exit_call_check.go (multi-file shared, unaffected). Tombstone with per-detector revival preconditions recorded in write_integrity.go registry. All file-private symbols verified zero-external-reference before deletion. Full repo build + internal/agent package regression green (15.1s).
test(agent): drop stale mcpRuntime entry from nil-fields allowlist
chore(agent): remove dead mcpRuntime wiring left by ecosystem-detector deletion
fix(#512): narrow live checkDebugStmts to unambiguous debug signals; delete i18n + time_format dead detectors — R71 census batch, sa-171 probe-verified (3/3 in-package repro tests, all passed, temp files removed). LIVE FIX: checkDebugStmts (called at agent_tool.go:551/729 on every source write) flagged languages' ONLY stdout primitives as debug leftovers and coached "Remove them before completing the task" — Python print (sa-171 A1: a normal CLI with 2 prints warned "2 x print() (Python)"; a 10-print script would report "10 x"), C printf (CLI usage output), Java System.out, Swift/Dart print, Rust println!, Ruby puts/p (which also warned on the comment # p(x)). Normal stdout and debug prints are token-indistinguishable, so the pattern table is narrowed to unambiguous debug signals: Go fmt.Print + builtin println (strong log convention; A5-verified TP), JS console.* + debugger, Python breakpoint/pprint, Rust dbg!, PHP var_dump/print_r/dump, Swift debugPrint/dump; removed: Python print, C/C++, Java/Kotlin, Swift/Dart print, Ruby, Rust println-family. Four tests asserting the old FPs rewritten as exemption assertions; all kept TPs verified still firing. DEAD DELETES (9th census instance, write_integrity.go zero refs): i18n_check.go — signature discards oldContent (identical rewrite re-reports, #509 a11y zero-delta class), warns "locale-sensitive" on ISO-8601 "YYYY-MM-DD" literals (locale-independent BY DESIGN), and its Go advice ("use locale-aware formatting" for t.Format("2006-01-02")) is unimplementable — Go stdlib has no locale-aware date API, and log/wire formats SHOULD be locale-stable. time_format_check.go — scalar delta (len(old)>=len(new)→nil) broken both directions: fix-one-add-one suppresses a NEW wrong layout (the #510 ctxkey class) while additions re-report pre-existing ones; extractTimeMethodName matches ANY .Parse receiver so strptime.Parse("%Y-%m-%d") (correct for that lib) gets "fixed" into Go tokens, breaking it. Its core signal (time.Parse("YYYY-MM-DD")→"2006-01-02") confirmed CORRECT — tombstone records it as the strongest partial-revival candidate (preconditions: per-instance multiset delta per #171 + receiver restriction via go/types or time. allowlist). All symbols verified zero-external-reference before deletion (incl. unquoteGoString). Full internal/agent package regression green (7.1s).
fix(#511): delete flaky_test + test_gaming dead detectors — R70 census batch, sa-170 probe-verified (12/12 in-package repro probes, all passed, temp files removed). Both were stripped by the fc5c4aa critical-only refactor and survived as fully-implemented+unit-tested dead code (8th instance of the #328/#330/#499/#503-#510 class), but revival is empirically FP/FN-confirmed. flaky-test-patterns: the regex path matches comments and string literals BY DESIGN (a comment "// time.Sleep removed, now using wg.Wait()" warns — coaching the agent for documenting the fix); the goroutine check's ±15-line proximity window has no scope awareness (wg.Wait() 16+ lines from the go stmt is invisible; probe: go@L9 + wg.Wait@L30 warned where 0 expected) and exprToString(FuncLit) returns "" so the warning literally reads "'go ' launched without WaitGroup"; isMapTypeExpr returns true for every *ast.Ident, so for _, tc := range tests over a []struct — THE table-driven idiom — warns on nearly every new table-driven test (probe: got[0] != tc.want over []struct warned as "range over map", the single largest FP source); its rand advice is self-contradictory (concedes math/rand/v2 stays non-deterministic while still warning; rand.Seed is deprecated since Go 1.20 auto-seeding — following the advice reproduces the flakiness or breaks parallel-test isolation). test-gaming: TestFoo→TestFooV2 (body unchanged) is reported as "removed test function(s)... fix the code instead of deleting tests" — a cheating accusation on legitimate refactoring; "expect " with a trailing space counts any prose string as an assertion (deleting 3 prose lines reports "removed 3 assertion(s)"); and the scalar assertion count is hedged — delete 2 real assertions, add 2 trivially-true assert.Equal(t, 1, 1), zero warnings: the anti-spec-gaming detector is bypassable by spec gaming itself (#506/#507 scalar/set-delta class). Partial-revival candidates recorded in the tombstone: checkGoTestGaming's deleted-func signal is the strongest single detector (precondition: normalized-body similarity exemption so renames don't fire); assertion removal needs a line-level multiset delta; the goroutine check needs scope-aware sync lookup + FuncLit name fix. exprToString (lock_without_unlock_check.go, shared with printf_format_check.go) and isTestFile (debug_sniffer.go) survive in registered files. All 22 file-private symbols verified zero-external-reference before deletion. Full internal/agent package regression green.
fix(#510): delete error_msg_quality + error_sentinel + ctxkey dead detectors — R69 census batch, sa-169 probe-verified (3/3 in-package repro tests, all passed, temp files removed). All three were registered at birth, stripped by the fc5c4aa critical-only refactor, and survived as fully-implemented+unit-tested dead code (7th instance of the #328/#330/#499/#503/#506 class), but revival is empirically FP/FN-confirmed: error-msg-quality's delta key embeds fset.Position (line:col) — any insert above a pre-existing generic error message mismatches the key and re-reports it (the #507 error_swallow defect class, violating the #186 fingerprint convention; probe: L6→L7 shift produced 1 warning where 0 expected, keys-equal:false proven); ctxkey's line-number delta is broken in BOTH directions — a shift re-reports AND a brand-new violation (changed key literal "user"→"session-id") landing on an old line number is silently suppressed (FN: the promised write-time guard goes silent in the most common manual-rewrite shape; probes: shift=1 warning expected 0, new-violation-on-old-line=0 warnings expected ≥1); error-sentinel's single-letter "e" heuristic plus bare "Canceled" sentinel name fire on non-error entities (for _, e := range events { if e == Canceled } — e is an Event, Canceled a State constant) with errors.Is advice that does not compile (#506 float-equality class), and it has no _test.go exemption — test files are where legal sentinel comparisons concentrate (probe: foo_test.go warned, msg-quality's identical path correctly silent). Its content-fingerprint delta (sentinel-cmp:X op Y) WAS correct — recorded as the strongest partial-revival candidate (preconditions: drop the "e" heuristic or go/types, _test.go exemption, fresh zero-FP probes). Census also exposed the coverage gap: the two delta-broken detectors were exactly the two without line-shift regression tests. callExprName/unquoteString/unescapeQuotedString (#509 migration) went down with their sole consumer as their fate-binding note predicted; exprText survives in suspicious_comparison_check.go; TestErrorSentinel_LineShiftDoesNotRereport removed with its host (two live empty-error-body regression tests kept). All 24 file-private symbols verified zero-external-reference before deletion. Full internal/agent package regression green.
fix(#509): delete a11y + error_wrap dead detectors — R68 census batch, sa-168 probe-verified (8 in-package tests, all passed, temp files removed). a11y_check.go: registered at birth (dc64b24: LangMarkup+LangJSTS "accessibility"), stripped by fc5c4aa; discards oldContent entirely (ZERO delta — identical rewrite re-reports every pre-existing issue, competing for the single maxIntegrityWarnings=1 slot each write, violating the #186 fingerprint-multiset convention every registered peer follows), regex-scans .js/.ts string literals blindly (an a11y library's own sanitizer test fixtures produce warnings — no string-context awareness), and in JSX the "=>" inside arrow handlers terminates [^>]* before role/tabIndex/onKeyDown so ALL clickable divs warn including correctly-fixed ones (coaching agents into breaking correct accessible JSX); SVG non-match and alt=""/data-alt word-boundary behavior verified correct, but advisory class (WCAG advice) is exactly what fc5c4aa removed "to eliminate context pollution". error_wrap_check.go: never registered; Pattern 2 (%v→%w) empirically 3/4 FP — API-boundary intentional %v (avoiding errors.Is coupling), multi-error aggregation where %w permits exactly ONE wrapped error so the suggested fix has no valid form, and the e-name heuristic flags non-error string vars with advice that would not compile (go vet printf/staticcheck deliberately do not enforce %v→%w; wrapcheck is opt-in); delta is a bool-set keyed by format-string/file-wide constants — masks growth (2nd concat site: 0 warnings) and fix-one-introduce-one (silent FN); Pattern 1+3 (concat + errors.New(err.Error())) confirmed the zero-FP core, recorded as the reserved partial-revival candidate. Migrated callExprName/unquoteString/unescapeQuotedString into error_msg_quality_check.go (sole remaining consumer, itself unregistered census — header note ties helpers' fate to that file's disposition). Deleted a11y tests (26) + target_scatter_bug_test.go (all 5 tests are a11y regex/behavior probes) + error_wrap tests; all symbols verified zero-external-reference before deletion. Full internal/agent package regression green.
fix(#508): delete ignored_error + naked_return dead detectors; register empty_error_check as zero-FP "empty-error-body" — R67 census batch, sa-167 probe-verified (11 in-package tests, all passed, temp files removed). ignored_error_check.go: method-name fallback asserted "returns an error" on zero-return-value methods (conn.Write on func (c fakeConn) Write(p []byte) {} — factually wrong, deeper than the #111 always-nil case its own comment documents), flagged the _ = deliberate-discard syntax errcheck leaves off by default (coaching class, same as #505), and ALL three-segment keys (os.File., bufio., sql., http.Server., os/exec.Command.*) were unreachable in an untyped AST — f.Close() resolves to "f.Close" — while its own marquee header example json.NewEncoder(w).Encode resolved via constructorReturns to json.Encoder.Encode against the stored json.NewEncoder.Encode key: a double mismatch, so detection rested entirely on 30 generic method names; unfixable without go/types (why errcheck is built on types.Check) → delete + tombstone, revival precondition: go/types integration. naked_return_check.go: advisory class the fc5c4aa strip explicitly removed ("eliminate context pollution"); fires on the defer+recover named-result idiom (verified, 24-line func) at threshold 20 vs revive nakedret default 30; funcName-level delta masks same-function growth (2→3 naked returns, zero warnings, verified); FuncLit non-recursion confirmed correct/conservative → delete + tombstone, revival preconditions: min-lines>=30 + instance multiset delta + closure coverage. empty_error_check.go REGISTERED as "empty-error-body": zero FP across all probes (pure-empty/comment-only/semicolon-only bodies precise, non-error vars exempt, fingerprint multiset delta handles fix-one+introduce-one), exactly the Pattern-1 revival path the #507 tombstone reserved ("the only zero-FP revivable part"), semantically distinct from nil-deref-after-error (post-check inaction vs misuse), delta-gated so steady-state rewrites are zero-noise (e2e verified: 2 new bodies reported, identical rewrite 0). 26 deleted symbols verified zero-external-reference; exprText dependency confirmed live (suspicious_comparison_check.go:269); checkEmptyErrorBody already covered by detector_delta_regression_test.go. Full internal/agent package regression green.
fix(#507): delete error_swallow + error_nopropagate dead detectors instead of #499-style resurrection — both were registered at birth, stripped by the fc5c4aa critical-only refactor, and survived as fully-implemented+unit-tested dead code (6th instance of the #328/#330/#499/#503/#506 class), but revival is empirically FP-confirmed by sa-166 (4/4 in-package repro tests): error-swallow Pattern 2 (bare return) fires on named-result propagation — func f() (err error) { if err != nil { return } } returns the NON-nil err (Effective Go recommended, required for recover()), yet the warning text claims "instead of returning nil" (factually wrong because funcReturnsError never checks whether results are named); error-nopropagate fires on the Go 1.20+ errors.Join accumulation idiom (errs = append(errs, err) ... return errors.Join(errs...)) where the suggested fix silently converts accumulation into fail-fast — coaching agents into breaking correct code (same class as the #505 forwarding-wrapper FP) — and on struct-field stores (s.lastErr = err) / channel handoffs (errCh <- err), deferred-propagation sinks that AST heuristics cannot distinguish from swallowing without interprocedural dataflow (the reason go vet/errcheck/staticcheck all deliberately implement no such check); its line-number delta also re-reports every pre-existing instance after any top-of-file insert while a deletion above a new true positive suppresses it (error_swallow's own header comment documents why fingerprints are required instead). The two LIVE helpers (looksLikeErrorVar/isNilIdent — sole external consumers were nil_deref_check.go:325,326,390,392) were migrated into nil_deref_check.go with the thin looksLikeError wrapper inlined; the other 18 symbols verified zero-external-reference before deletion (countErrorNoPropagate was not even referenced by its own test). Tombstone comment in write_integrity.go records the revival preconditions: named-result exemption + drop the nil claim (or revive unambiguous Pattern 1 alone); deferred-sink exemptions + fingerprint delta. Full internal/agent package regression green.
fix(#506): delete error_order + float_equality dead detectors instead of #499-style resurrection — both were registered at birth (3e3e2ad / ac25e08), stripped by the fc5c4aa critical-only refactor, and survived as fully-implemented+unit-tested dead code (4th/5th instance of the #328/#330/#499/#503 class), but unlike append_ignored (#499, zero-FP pure win) revival is empirically 100%-FP: sa-165 probe-verified that checkErrorOrder fires on the io.Writer contract idiom (n, err := w.Write(b); total += n — n is VALID on error by the stdlib's own contract, io.Copy does exactly this) with a factually wrong "causes a panic" claim for non-pointer results, while its core true positive (defer resp.Body.Close() before the check) is ALREADY covered by the registered nil-deref-after-error — which correctly stays silent on the writer idiom; and checkFloatEquality's feqIsMathFunc matches EVERY math.* call as float64 (IsNaN/IsInf/Signbit return bool, Float64bits returns uint64 — its epsilon fix advice does not compile), flags exact-representable sentinels (== 0.5, == 1.0) where epsilon comparison would BREAK correct code, discards oldContent entirely (no delta), and cannot in principle separate sentinels from computed values without dataflow (the reason golangci-lint keeps float-eq off by default). All 13 file-private helpers verified zero-external-reference before deletion; tombstone comment in write_integrity.go extended with both revival preconditions. Issue also records sa-165's census of the fc5c4aa batch: 93 stripped, 32 currently registered, 78 still-unregistered names of which 54 have surviving implementations (46 whole dead *_check.go files + 6 renamed variants + 4 dead funcs in live files), flagged error_swallow_check.go as the priority next batch (contains live helper looksLikeErrorVar used by nil_deref_check.go:392 — migrate before file deletion). Full internal/agent package regression green.
fix(#505): printf_format_check double deterministic false positive — two idiomatic Go patterns that go vet (the detector's own baseline, cited in its header) explicitly exempts both fired 100%: (1) variadic spread calls like fmt.Sprintf("%s=%v\n", kv...) / log.Printf("%s %s", parts...) counted the spread as exactly one argument (extraArgs=1 vs verbs=2 → "2 format verb(s) but 1 argument(s)") because the AST counter never reads call.Ellipsis — runtime slice length is statically unknowable, which is exactly why vet's printf checker skips spread calls; the warning's "This causes a go vet error" claim was factually wrong, and the same counting flaw hit explicit-index reuse ("%[1]s and %[1]s" with one arg). (2) forwarding wrappers — Go's most common helper shape, stdlib testing.T.Logf/slog/errors style: func logf(format string, args ...any) { log.Printf(format, args...) } and the prefix variant log.Printf("[WARN] "+format, args...) — always tripped the nonconstant-format injection warning because the format parameter is a lowercase Ident (not all-caps, not a literal, not err.Error()); the warning's fix advice (Printf("%s", format)) would BREAK the wrapper's forwarding semantics, actively coaching agents into corrupting correct code. Fix, empirically verified by sa-164 (in-package repro tests + vet contrast runs, fact chain a-g): (1) skip verb-count when call.Ellipsis.IsValid() or the literal uses %[N] explicit indexes; (2) scope-aware forwarding exemption — findPrintfFormatIssues now uses an ast.Visitor carrying the enclosing FuncDecl/FuncLit parameter names, and a call is exempt when its format expr is a parameter (or string-literal prefix + parameter) AND its final spread argument is another parameter; local-variable formats and genuine verb mismatches still warn. Characterization tests: VariadicSpread, ExplicitIndex, ForwardingWrapper (both forms) all silent; TruePositivesSurviveExemptions (local var format + no-spread mismatch) still fires; all 12 pre-existing tests unchanged and green.
fix(#504): emitGeneration dead field wired into per-callback emitIfCurrent guard — the #489 commit message claimed "emit() generation guard drops stale-run stream events" and the struct comment claimed protection over "persist tail, stream emit", but the diff only ever wired the PERSIST side (persistSession/persistGeneration + sessionLockMismatchLocked); emitGeneration was declared at chat.go:178 with zero uses at the fix commit and forever after (same #328/#499 dead-wiring class), leaving emit() unguarded: applySemanticToLiveHistory writes into the CURRENT session's live history (reseeding it from the new session's messages when empty), the done branch mutates desktopAssistantID/desktopTextSeq (corrupting the next real turn's message linkage), and OnStreamEvent/tunnelHost.PushStreamEvent fire regardless of run ownership. One-click trigger (verified by independent subagent sa-163, fact chain a-f, severity Medium): App.NewSession() runs Cancel → StartNewSession → ClearCurrentSession back-to-back synchronously while the agent goroutine is still draining in-flight events (agent.go emits summary/error/tool-result events after ctx cancel; Cancel() nils b.cancel synchronously so LoadSession's busy guard also passes during the drain) — the cancelled run's tail events land in the NEW session's live history and frontend stream as ghost content. Fix: each of the three RunStream*/RunStreamWithContent callback sites captures the generation under the lock before starting the run and routes through emitIfCurrent(gen, ev), which drops events whose generation no longer matches; the dead emitGeneration field is removed and the struct comment now states where each half of the protection actually lives. Characterization test: TestEmitIfCurrent_DropsStaleRunEvents (stale event dropped, current event delivered). Disk persistence was already correctly protected by the #489 snapshot + #305 tombstones — this fix covers the in-memory/UI half.
fix(#503): delete checkAssertionWeakening dead code instead of #499-style resurrection — the detector was unregistered by the fc5c4aa critical-only refactor but its 657 lines (impl + tests) survived as dead code, and re-registering it (the tempting fix after #499 re-wired checkAppendIgnored) would activate an empirically-confirmed false-positive class: detectExpectedValueChange compares ALL literals positionally, so rewriting a human-readable error message ("failed to fetch" → "failed to fetch user profile") in source+test sync fires a reward-hacking accusation on an everyday edit; the only exemption (literal count change) never triggers on rewording since counts stay equal. The whole file is deleted (all 17 symbols file-private, zero external references, verified by symbol-level grep) and a tombstone comment in write_integrity.go's allChecks registry records WHY there is deliberately no assertion-weakening entry — including the revival precondition (position-aware exemption for testify trailing msgAndArgs and t.Error*/t.Fatal* first-arg format strings) — so a future #499-pattern pass does not blindly resurrect it.
fix(#499,#500,#501,#502): four verified detector defects — checkAppendIgnored registered at last (the write-time discarded-append() detector was fully implemented and unit-tested but never wired into the write_integrity allChecks registry — third instance of the #328/#330 dead-detector class; one-line sliceCheck registration in the Go-correctness section), wt/expired/futile read tracking is batch-aware for multi_file_read (agent.go recorded only files[0] via extractToolFilePath so a 5-file batch read counted as one — suppressing the cross-file stale-read warning below minReadsBeforeWarning=2 exactly when agents rely on batch reads, and starving expired-read/futile-cycle identically; the loop now records every path from extractFilePathsFromArgs, post-edit re-read hint stays first-path to avoid spam, and the three dead helpers extractToolFilePath/extractWTReadPath/extractMultiReadPaths are removed with their tests), spec_gaming isCIRelatedTask matches 'ci' as a whole word (bare substring empirically hit 12/12 everyday prompts — efficient, special, decide, precision, pricing, sufficient, crucial all contain 'ci' — permanently disabling the CI-tampering pattern for ordinary tasks; tokenized match via FieldsFunc with substring matching kept for multi-word phrases, counter-cases added), export_guard burns its once-per-run dedup marker only after a comparison actually completes (the marker was set BEFORE gitHeadExportSymbols, so a failed git-show on an uncommitted new file silenced the guard for that file for the rest of the run even after a mid-run commit made the comparison possible — exactly the breaking changes the guard exists to catch; all four verified by independent subagents sa-160 fact chain 4/4 and sa-161 fact chain 5/5 with empirical reproduction)
docs(#498): UnbindAdapter doc comment now matches its idempotent implementation
fix(#497): MCP hot-reload watcher is scope-aware — workspace sessions no longer lose workspace-only servers on global-file edits (watcher fed the GLOBAL file's list into Reload while workspace-bound managers are built from the WORKSPACE mcp_servers.yaml — two set computations that only coincide for global-scope users — so any daily global write (another session's AddMCPServer, CLI mcp add, Claude migration, manual edit) name-diffed the session's workspace-only servers into 'removed' → tools unregistered + connections closed, silently and irrecoverably until restart, while global-only servers leaked INTO the workspace session; the watcher was also blind to manual workspace-file edits entirely. resolveScopeMCPServers now mirrors the wailskit LoadConfigForWorkspace resolution — workspace ggcode.yaml present ⇒ read the workspace mcp_servers.yaml (or .ggcode/mcp_servers.yaml), else the global file — making the reload input match the manager's initial-set computation; scope files join the watch set so workspace edits now trigger reloads; the shared mtime watermark advances across ALL watched files, fixing the reload-storm edge when the global file is absent; verified by independent subagent sa-158 fact-chain a-e, severity High; characterization tests: global edit keeps workspace servers, workspace-file edit reaches the manager, no reload storm without a global file)
fix(#496): AddMCPServer workspace-scoped writes now propagate to the running session (hot-reload watcher polls only the GLOBAL mcp_servers.yaml — interactive_core.go constructs MCPHotReload with config.ConfigDir() while SaveMCPServers writes filepath.Dir(workspace yaml)/mcp_servers.yaml, two paths that never intersect — so a workspace-bound session's MCP edit/add was silently dead until restart; even the UI Reconnect button was structurally useless: MCPPlugin.Connect short-circuits on the cached adapter and reconnects reuse the plugin's construction-baked m.cfg, making Reload the only effective primitive; AddMCPServer now snapshots the bridge once per the #458 pattern and calls reloadSessionMCPServers — the same MergeStartupServers+manager.Reload computation a watcher-triggered reload performs, idempotent for persisted Claude-migrated servers — symmetric with RemoveMCPServer's #408 explicit Disconnect; verified by independent subagent fact-chain 6/6, severity Medium; characterization tests in mcp_reload_test.go: workspace add reaches manager without watcher tick, edit replaces the plugin, workspace persistence without global leak)
fix(#493,#494,#495): spiral_hallucin per-topic verified + spiralMinGap enforcement ends the global-reset false positive (spiralTrackedTopic gains per-topic verified — recordSpiralVerification and prose-verification mark all tracked topics, new uncertainty no longer wipes any protection, so the exact issue scenario "unrelated 'I think the retry logic' re-arming warnings on verified postgres" stays silent; the dead spiralMinGap constant and write-only sourceTurn field now gate committed counting — commitment at gap<2 is ordinary inconsistency and does not count, same-turn gap=0 included; the obsolete "3+ topics AND no verification" global mitigation that the reset used to bypass is removed; characterization suite 4bb20bd flipped with a no-verification contrast case proving the true positive survives), tool_equiv implements the documented :32 exact-match suppression contract (rawFingerprints write-only dead map replaced by per-normFp raw tracking — when EVERY occurrence of a normalized fingerprint is byte-identical the repetition belongs to tool_redundancy and this detector yields, killing the double-warning where byte-identical grep x3 fired both "Semantic duplicate... already in context" and "already in your context from earlier calls"; threshold 2→3 so divergent-raw (reordered keys / volatile fields) semantic equivalence — the case this detector exists for — is what warns; both messages now carry "unless context compaction has trimmed earlier results" so post-compaction re-queries are not falsely asserted to be in context), and tool_overuse write bookkeeping moves post-execution gated on !result.IsError (maybeWarn is pre-execution and could never see the outcome, so failed edit_file — the most common LLM arg error — recorded filesWritten, then the recovery read_file that edit_fail_recovery itself recommends received false-premise "Trust the content from your edit" guidance contradicting that recommendation in the same iteration; recordWriteResult added at the existing spiral-verification gate which already has result.IsError, failed writes also clear stale entries from prior successful edits, and trivial command matching is now whole-word-sequence so cat .pwd_history / ls /tmp/uname-dir / echo which python3-config no longer match)
fix(#497): RemoveIMAdapter cascade reorder makes the #460 retry advice structurally reachable (unbind-first uses im.Manager.UnbindAdapter's documented idempotency — runtime_bindings.go:194 no-bindings-is-no-op — so a failed unbind now leaves the adapter config intact for a real retry; the old delete-then-unbind order left every retry dead at config.RemoveIMAdapter's "not found" guard (config_save.go:511) before it ever reached the unbind, meaning a binding-store failure during adapter removal permanently leaked the ghost binding the #396 cascade was built to prevent, while the error text kept advising "retry to clear the leftover binding"; both failure paths are now retryable — unbind failure keeps config for full-chain re-entry, config-delete failure re-runs a harmless unbind no-op — and the intermediate state degrades from config-gone+ghost-binding to config-present+unbound), characterization test TestRemoveIMAdapterFailedUnbindRetryWorks asserts the surviving-config invariant the old order violated plus the recovered retry completing full removal, nil-manager path covered separately
test: characterization tests proving spiral_hallucin gap-semantic and verified-granularity defects
fix(#490,#491,#492): plan_abandon execution-evidence gate ends the ~100% false-positive on faithful multi-step completion (maybeWarnPlanAbandon now takes runStats and only warns when a declared step CATEGORY has zero matching evidence — edit-class step with empty FilesEdited or run-class step with empty CommandsRun; pure-read plans have no evidence channel and stay silent, nil stats keeps the declare→claim-done-without-doing shape triggering — fulfillment_gate.go's evidence pattern reused, characterization test c7946c9 flipped to assert the fixed behavior with a true-abandonment contrast case), correction_spiral joins the #483/#485/#488 run_command-content-classification fix family as its third missed consumer (successful cat/ls between edit and build no longer breaks the correction chain via pendingEdit=false — sub-problem A detector-blindness; failed exploratory commands no longer pollute errorSequence — sub-problem B; psArgs deserialization hoisted above the block, start_command excluded entirely since its result reflects job startup not verification outcome, dead csIsVerifyTool removed), and csClassifySeverity crash/test ordering fixed (test-failure shape fail+test/assert/expect now checked BEFORE crash markers so "--- FAIL: TestSignalHandler" classifies as sevTest not sevCrash, bare "signal" substring tightened to contextual "received signal"/"signal:" forms — sub-problem C); momentum_loss shell-channel blindness fixed (run_command/start_command classified by command CONTENT via mlIsObservationalCommand — cat/ls/head/tail/pwd/rg/grep/find/ag/ack/bat/less/more/tree first words plus git log/diff/show/status/blame subcommands demote to consumptive, unknown and genuine build/test commands stay productive so the conservative whitelist cannot introduce new false positives — late-phase shell exploration now triggers last-mile stall exactly like its tool-channel equivalent read_file/grep per the documented contract)
test: characterization test for plan_abandon false positive on faithful completion
fix(#487,#488,#489): premature_refactor revived from 100%-dead wiring (prematureRefactorRecordVerify was called unconditionally on EVERY tool result, so the first read_file set hasVerified and silenced the detector for the whole run; now gated on command CONTENT via psIsVerifyCommand(extractCommandFromArgs) like its two neighbors, plus F1 localized-fix exemptions — line-wise core diff, ≤2 differing lines with <32 changed bytes, or small pure tail append/delete — and F2 keyword hardening: whole-word matching with comment lines stripped, so extractTargets/os.Rename identifiers and "optimize later" TODOs no longer classify, and recordEdit hoisted out of the per-file loop so one multi_file_edit no longer doubles the counter), target_scatter window no longer wiped by observational commands (ls/cat/pwd/git log interleaved with diagnostics is the most common investigation shape — scatterIsVerification was the exact #350 tool-name-vs-content bug family; now only genuine verify commands clear the window via psIsVerifyCommand, hasMutation set-then-clear dead store and its dead check gate removed, scatterIsMutation extended with file_ops/git_*/undo_edit/write_command_input/enter_worktree, dead reset() wired into the per-turn reset), chat.go persist cross-write re-armed by post-Cancel nil fallback fixed via per-run closure-captured persist snapshot + run generation counter (run start binds snapshot; ClearCurrentSession bumps generation and drops it; late tail persists from the cancelled run now drop instead of falling through runSes==nil → currentSes==NEW session — the exact #270 contract re-armed), context.Canceled filtered from appendLiveError, emit() generation guard drops stale-run stream events, and the zero-caller wailskit.NewSession dead export removed
fix(#484,#485,#486): compounded_uncertainty wires the born-dead unverified_success channel (premature_success feed at its trigger point, weightUnverifiedSucc was never referenced by any production caller; assumptions category retired honestly — docs/warning text updated to the three real channels after 387282a removed the assumption detector), strategy_fixation green-verification FULL reset (whole-tree build terminates every file's streak, not just lastFile — stale cross-file counts fired "approach not converging" right after a green build) plus directory-qualified failure attribution (same-base-name collision internal/agent/agent.go vs internal/tool/agent.go no longer misattributes), sfExtractMutationPaths walks multi_file_edit files[] + notebook_path (previously first-path-only / never extracted), and run_command verification gated by psIsVerifyCommand (successful cat/ls no longer resets streaks); patch_exhaust implements the documented-but-never-built lastPatch excursion tolerance (single one-read excursion resumes the stashed count, second departure hard-resets so two-dir ping-pong cannot accumulate, patchOf normalizes ./ prefixes and trailing-slash directory inputs via weNormalizePath)
test(#480): concurrency coverage for the writeMessage lock split — 4 writers x 50 frames alternating writeMessage/sendNotification paths under -race, every pipe line must parse as exactly one JSON-RPC frame (interleave detection), plus sendNotification no-deadlock structural check for the Unlocked variant contract
fix(#482,#483): wasted_explore cross-format path matching (weNormalizePath/wePathsMatch — ./ rg output matches absolute lsp/read paths, base-name rescue; code_search 'N. path' enumerator stripped so its searches are tracked at last) and premature_success hyphen-prefix verify patterns restricted to COMMAND position — 'git add test-utils.go'/'cat verify-config.yaml'/'gofmt -w test_utils.go' no longer arm everVerified and silence the detector for the whole run (ninja check-all arg-position casualty accepted per issue analysis); issue's 6 characterization tests flipped to fixed-behavior assertions, all 11 false-positive table entries verified
test: characterization tests for wasted-explore path normalization bug (#480 verification)
fix(#479,#480,#481): Summarize nonTailMutSeq TOCTOU guard (discards stale snapshot on any non-tail mutation during the LLM window — deletes/mid-inserts/mechanical clears; pure tail appends still rescued by extraMsgs), MCP server-request responses write under c.mu (writeMessage/writeMessageUnlocked split — stdio NDJSON interleave corruption and gorilla single-writer violation both fixed), Windows stale-lock cleanup unlocks+ closes BEFORE os.Remove (sharing-violation-proof order matching Release) with debug logging
fix(#478): SearchSessions uses a bounded bufio.Reader (readLineLimited) so a single >10MB JSONL line only discards THAT line — previously the Scanner cap aborted the scan and the caller's error-continue silently dropped every collected hit with no log; skipped lines are now debug-logged, hits before AND after the blob are kept
fix(#477): drainPendingInterrupt pops the parallel pendingSource/pendingExclude pair when it consumes a visible queued message (agent iteration boundary), and QueueMessage appends its own desktop pair — FIFO alignment between queue and slices restored, no more stale im/telegram pairs shifting later drains or leaking unboundedly
fix(#476): tunnel vision counts search-tool breadth (grep/lsp/code_search result files via extractSearchResultPaths, reusing searchResultTools) and exempts test-fix tasks from the ratio warning — a 12-file grep sweep plus 2 read_file's no longer scores as '2 files, broaden exploration'
test(#463,#465,#467,#469,#471,#472): regression coverage for the detector fixes — comment-prefix stripping end-to-end, strict verify prefix semantics, formatter exclusion from convergence lock, read-window detection, serial-read neutral unknown tools, tool-storm consecutive-iteration check
fix(#475): queued IM messages carry their own source+excludeAdapter through the drain (FIFO index-aligned slices replace the dead single-value field; source stays 'im' so exclusion is actually consumed — no more self-echo on the originating adapter)
fix(#474): guidance text merges into first tool message content instead of a user message between tool_calls and tool results — OpenAI ordering contract restored for strict backends
fix(#471..#473): extractCommandFromArgs strips mandated '# ' comment prefix (4 detectors revive on conventionally-formatted commands), convergence lock excludes pure formatters (gofmt/prettier/cargo fmt no longer arm it), drift_recurrence exempts compliant 0-new-dir agents + dirSignature absolute-path collapse