instar 1.3.108 → 1.3.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/commands/init.js +48 -0
  2. package/dist/commands/init.js.map +1 -1
  3. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  4. package/dist/config/ConfigDefaults.js +10 -0
  5. package/dist/config/ConfigDefaults.js.map +1 -1
  6. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  7. package/dist/core/PostUpdateMigrator.js +67 -0
  8. package/dist/core/PostUpdateMigrator.js.map +1 -1
  9. package/dist/core/SessionManager.d.ts +11 -0
  10. package/dist/core/SessionManager.d.ts.map +1 -1
  11. package/dist/core/SessionManager.js +18 -15
  12. package/dist/core/SessionManager.js.map +1 -1
  13. package/dist/core/frameworkSessionLaunch.d.ts +20 -0
  14. package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
  15. package/dist/core/frameworkSessionLaunch.js +27 -0
  16. package/dist/core/frameworkSessionLaunch.js.map +1 -1
  17. package/dist/monitoring/RevertDetector.d.ts.map +1 -1
  18. package/dist/monitoring/RevertDetector.js +12 -1
  19. package/dist/monitoring/RevertDetector.js.map +1 -1
  20. package/dist/paste/PasteManager.d.ts.map +1 -1
  21. package/dist/paste/PasteManager.js +13 -6
  22. package/dist/paste/PasteManager.js.map +1 -1
  23. package/dist/scheduler/MentorAutonomousGuardian.d.ts +91 -0
  24. package/dist/scheduler/MentorAutonomousGuardian.d.ts.map +1 -0
  25. package/dist/scheduler/MentorAutonomousGuardian.js +88 -0
  26. package/dist/scheduler/MentorAutonomousGuardian.js.map +1 -0
  27. package/dist/scheduler/MentorOnboardingRunner.d.ts +61 -2
  28. package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
  29. package/dist/scheduler/MentorOnboardingRunner.js +48 -3
  30. package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
  31. package/dist/server/AgentServer.d.ts.map +1 -1
  32. package/dist/server/AgentServer.js +63 -2
  33. package/dist/server/AgentServer.js.map +1 -1
  34. package/dist/server/routes.d.ts.map +1 -1
  35. package/dist/server/routes.js +2 -1
  36. package/dist/server/routes.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/data/builtin-manifest.json +64 -64
  39. package/upgrades/1.3.109.md +80 -0
  40. package/upgrades/1.3.110.md +55 -0
  41. package/upgrades/NEXT.eli16.md +60 -63
  42. package/upgrades/side-effects/dangerous-command-guard-gh-pr-merge-gate.md +102 -0
  43. package/upgrades/side-effects/failure-learning-source-tree-readok.md +113 -0
  44. package/upgrades/side-effects/loop-session-no-mcp.md +65 -0
  45. package/upgrades/side-effects/mentor-autonomous-fix-loop.md +79 -0
  46. package/upgrades/side-effects/no-empty-catch-blocks-lint.md +101 -0
@@ -0,0 +1,55 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ **The mentor autonomous-fix loop now spawns its per-cycle session with NO project
8
+ MCP servers — fixing a headless boot-hang found by dogfooding the loop live.**
9
+
10
+ The loop (shipped dark in v1.3.109) starts a full-tool Opus session each cycle. A
11
+ headless `claude -p` spawn inherits the project `.mcp.json`, which includes
12
+ interactively-authenticated remote MCP servers (Fathom's `mcp-remote`, the
13
+ claude.ai connectors). Those can't complete their OAuth handshake headless, so
14
+ the session hung on MCP boot — observed live at ~4.5 min, 0.1% CPU, no transcript,
15
+ parked event loop — and never ran its cycle. The spawn was otherwise correct
16
+ (opus, full tools, the real goal); only MCP-loading jammed it.
17
+
18
+ The loop session now spawns with `--strict-mcp-config --mcp-config '{"mcpServers":{}}'`
19
+ (zero MCP servers). It needs none — it drives the mentee over Telegram and ships
20
+ fixes with built-in tools. A headless spawn that stalled now boots in ~9s. The
21
+ flag is opt-in (`spawnSession({ disableProjectMcp: true })`); every other spawn
22
+ keeps its MCP. The `--allowedTools` and new MCP-disable splices are unified in one
23
+ tested pure helper (`claudeHeadlessExtraFlags`).
24
+
25
+ ## What to Tell Your User
26
+
27
+ Only relevant if you run the (off-by-default) mentor autonomous-fix loop. I
28
+ dogfooded it live and found its background worker session was hanging on startup
29
+ — it was trying to load login-required tools that can't sign in without a human,
30
+ so it froze before doing any work. I fixed it so the loop's worker starts with a
31
+ clean, minimal toolset and boots in seconds instead of hanging. Nothing changes
32
+ for any other session — they keep all their tools. This is the observe-and-fix
33
+ loop doing its job, just with me driving it this round.
34
+
35
+ ## Summary of New Capabilities
36
+
37
+ | Capability | How to Use |
38
+ |-----------|-----------|
39
+ | No-MCP headless spawn option | `spawnSession({ disableProjectMcp: true })` (opt-in; used by the mentor loop) |
40
+ | Mentor loop session boots reliably | Automatic when `mentor.autonomousFix.enabled` — no more MCP boot-hang |
41
+
42
+ ## Evidence
43
+
44
+ - **Live reproduction (before):** the first real loop session (`mentor-autoloop-…`)
45
+ spawned as `claude --dangerously-skip-permissions --model opus -p '…'` and sat
46
+ ~4.5 min at 0.1% CPU, no transcript written, main thread in `_pthread_cond_wait`;
47
+ child procs `@playwright/mcp` and `mcp-remote …fathom` alive at 0% CPU.
48
+ - **Fix verified (after):** `claude --strict-mcp-config --mcp-config '{"mcpServers":{}}'
49
+ --model haiku -p '…'` in the same project returned in ~9s with no MCP boot.
50
+ - **Tests:** `tests/unit/claude-headless-extra-flags.test.ts` (6, every branch incl.
51
+ the empty-MCP-config validity); `tests/e2e/mentor-onboarding-lifecycle.test.ts`
52
+ (+1: the production loop spawn passes the no-MCP flag). Framework-launch
53
+ regression suite (56) green. `tsc` + lint clean.
54
+ - Side-effects: `upgrades/side-effects/loop-session-no-mcp.md`. Spec:
55
+ `docs/specs/LOOP-SESSION-NO-MCP-SPEC.md`.
@@ -2,81 +2,78 @@
2
2
 
3
3
  ## What this change is
4
4
 
5
- Your agent has a feature called the "Failure-Learning Loop" — it watches for
6
- things that go wrong (a CI run fails, a recent commit gets reverted, an
7
- internal health-check trips) and writes a little record so you can later ask
8
- "why do my agents keep breaking?" and get a real answer instead of vibes.
9
-
10
- The loop has a config switch for each kind of failure it watches. Some of
11
- those switches were checked in months ago but never connected to anything —
12
- flipping them on did literally nothing, the loop just pretended the source
13
- was active. That's exactly the failure mode the post-mortem yesterday named
14
- as "specced but not wired" — a real, repeat offender.
15
-
16
- This change does two things:
17
-
18
- 1. **Loud warning when you flip on a switch that isn't wired up.** If you set
19
- `monitoring.failureLearning.sources.regression: true` or
20
- `monitoring.failureLearning.sources.degradation: [...]`, the agent now
21
- says, at startup: "you turned this on but nothing implements it yet —
22
- set it back off until the impl ships." Before, those switches were silent
23
- no-ops.
24
-
25
- 2. **A new automated check that catches a different bug class.** When you do
26
- a fresh `instar init`, certain hooks get installed. When the agent
27
- auto-updates, a different bit of code re-installs hooks. We had at least
28
- one case in the last release where the two lists disagreed — a fresh
29
- agent was missing a hook that existing agents had, because nobody had
30
- noticed the divergence. The new check reads both lists and refuses
31
- to commit any change that creates a new divergence (with a small
32
- allowlist for documented technical debt).
5
+ In JavaScript/TypeScript, you can write `try { ... } catch {}` — a block
6
+ that says "do this, and if anything goes wrong, throw the error away
7
+ and continue silently." It's three characters of body and zero
8
+ explanation, and it's a really cheap shortcut that almost always means
9
+ "I don't want to think about this right now."
10
+
11
+ The problem: when an error gets thrown away silently, you don't find
12
+ out about it from a stack trace or a log line. You find out about it
13
+ when something downstream breaks, often hours later, often from a user.
14
+
15
+ The worst recent example was the **PromptGate $452 incident**. There
16
+ was a 5-second loop checking whether the LLM had hit its rate limit.
17
+ Inside that loop, a bare `catch {}` was swallowing every actual
18
+ rate-limit error. The loop kept burning credits at full speed —
19
+ hundreds of times per minute, sometimes for hours — because nothing
20
+ ever surfaced the problem. By the time someone noticed, we'd burned
21
+ $452 we shouldn't have.
22
+
23
+ This PR adds a unit-test lint that refuses any bare `catch {}` in
24
+ production code (`src/**/*.ts`, excluding tests and code-generation
25
+ hosts) unless it carries a comment annotation explaining WHY the
26
+ silent swallow is safe.
33
27
 
34
28
  ## What already exists
35
29
 
36
- - The Failure-Learning Loop itself, with ledger + analyzer + API at `/failures/*`.
37
- - The `ci` and `revert` ingestion sources, which actually work and capture events.
38
- - The `regression` and `degradation` source flags in config defaults
39
- (just no implementation behind them).
40
- - `PostUpdateMigrator` (the auto-update path) and `installHooks()`
41
- (the fresh-init path), both of which write hooks to disk independently.
30
+ - An older lint called `no-silent-fallbacks.test.ts` that catches a
31
+ related pattern: catches that return a degraded value (`return
32
+ null`, `return []`, etc.).
33
+ - The `@silent-fallback-ok` comment annotation, already used in
34
+ several places (`TrustRecovery.ts`, `SyncOrchestrator.ts`, etc.).
35
+ - `DegradationReporter` for emitting properly-tracked degradation
36
+ signals.
42
37
 
43
38
  ## What's new
44
39
 
45
- - Boot-time warning if `regression` or `degradation` source flag is on but
46
- unimplemented.
47
- - Unit test that pins the source-flag-to-poller wiring so future regressions
48
- fail the test suite instead of silently disabling the loop.
49
- - Unit test that diffs the fresh-init hook list against the auto-update hook
50
- list and fails on any new divergence.
51
- - A small allowlist of currently-accepted divergences (six entries, with
52
- rationale per entry; soft cap of ten so the list can't quietly grow).
40
+ - A new lint test `tests/unit/no-empty-catch-blocks.test.ts` that
41
+ scans for truly empty `catch{}` blocks and fails on any new ones.
42
+ - The seven existing bare `catch{}` sites are annotated in this same
43
+ PR — five in `src/paste/PasteManager.ts` (file cleanup that's
44
+ genuinely safe to silently fail) and three elsewhere. Each carries
45
+ a one-line rationale.
46
+ - A focused regression check on `src/core/PromptGate.ts` — the file
47
+ that gave the post-mortem its poster-child incident. It can never
48
+ silently regress.
49
+ - The lint's ratchet baseline starts at zero. New bare `catch{}` in
50
+ unannotated form fails the unit suite at commit time.
53
51
 
54
52
  ## What you need to decide
55
53
 
56
- Nothing. This is structural backstop, no configuration involved. Existing
57
- agents pick up the warning on next process restart (auto-update will trigger
58
- that). Future PRs that try to introduce a new fresh-init-vs-auto-update gap
59
- will fail their tests.
54
+ Nothing. Lint-only. No runtime change, no config, no fleet migration.
60
55
 
61
56
  ## How to verify it worked after deploy
62
57
 
63
- If you haven't touched `monitoring.failureLearning.sources` in your config,
64
- you should see exactly the same behavior. If you flipped one of the
65
- unimplemented sources on, you'll see a one-line warning at startup.
66
-
67
- To start actually USING the Failure-Learning Loop on your agent, set
68
- `monitoring.failureLearning.sources.ci: true` and `sources.revert: true`
69
- in `.instar/config.json`. The substrate has been ready since late April;
70
- this PR just adds the tests around it. Echo's local config is being flipped
71
- out-of-band as a dogfooding step.
58
+ Try writing a function with a bare `catch{}` in `src/`. Stage it. The
59
+ pre-commit gate's test step will fail with a clear message naming
60
+ PromptGate and the post-mortem. Fix: add `// @silent-fallback-ok —
61
+ <why>` above the catch, OR put a real body inside it.
72
62
 
73
63
  ## Why this matters more than it might look
74
64
 
75
- The 14-day fix-shape rate to main is around 19% — we've been shipping a lot
76
- of bugs and finding out from users instead of from instrumentation. The
77
- Failure-Learning Loop is the meta-trace that turns "we keep shipping bugs"
78
- into "here are the three patterns most of them share." But it was sitting
79
- unused because the ingestion sources were silent. This PR closes one half
80
- of that gap (the wiring-test + the unimplemented-source warning); flipping
81
- the working sources on for each agent closes the other half. Several more
82
- PRs from the same post-mortem will follow.
65
+ This is the fifth post-mortem PR landing in roughly four hours. Each
66
+ one closes a recurring bug class, not just one incident:
67
+
68
+ - #542 — silent-403 on secret-externalization (config-reader didn't
69
+ survive the security upgrade).
70
+ - #545 — failure-learning loop wiring + migration-parity tests.
71
+ - #550 — failure-learning git reads opt into the source-tree read
72
+ escape hatch.
73
+ - #551 — `gh pr merge` refused on red checks (the watch-exit-merge
74
+ class).
75
+ - THIS — bare `catch {}` refused in production paths.
76
+
77
+ Each of these patterns has cost real time + real money. Closing them
78
+ at the structural level (a lint, a test, a hook) is much cheaper than
79
+ relying on "we'll be careful next time."
@@ -0,0 +1,102 @@
1
+ # Side-effects review — dangerous-command-guard gh-pr-merge gate
2
+
3
+ ## What changed
4
+
5
+ `dangerous-command-guard.sh` (PreToolUse hook on the Bash tool) gains a new
6
+ gate block that refuses `gh pr merge` invocations when any PR check is
7
+ non-passing.
8
+
9
+ - The gate runs `gh pr checks <num> --json name,state` (uses the current
10
+ branch's PR if no number is given on the command).
11
+ - A check is "ok" iff its state is `SUCCESS`, `SKIPPED`, `SKIPPING`, or
12
+ `NEUTRAL`. Anything else (`FAILURE`, `PENDING`, `QUEUED`, etc.) is
13
+ non-passing.
14
+ - `gh pr merge --auto` is allowed through — it's the documented async safe
15
+ path that only fires when checks pass.
16
+ - All other shapes (`gh pr merge <num> --squash`, `gh pr merge --admin
17
+ <num>`, no-flag) are gated.
18
+ - Pattern match is bounded to command-start boundary (`^`, `;`, `&&`, `||`,
19
+ `|`, `(`, or whitespace), so commands that merely mention "gh pr merge"
20
+ in a string literal (e.g. `echo "fix gh pr merge bug"`) do not trigger.
21
+
22
+ The gate lives inline in `dangerous-command-guard.sh`, matching the existing
23
+ safety-gate pattern (catastrophic / deployment-coherence / risky-commands
24
+ blocks). Same content is duplicated in `src/commands/init.ts` (fresh-init
25
+ copy) and `src/core/PostUpdateMigrator.ts` `getDangerousCommandGuard()`
26
+ (auto-update copy), per the existing parity allowlist in
27
+ `migration-parity-hooks.test.ts`.
28
+
29
+ ## Why
30
+
31
+ This closes the **2026-05-27 PR #539 watch-exit-merge class**. Justin merged
32
+ PR #539 (the better-sqlite3 ABI heal) on the back of `gh run watch` returning
33
+ exit 0. But `gh run watch` returns 0 on workflow COMPLETION regardless of
34
+ conclusion. The PR's branch-protection checks were RED (Type Check + 2 unit
35
+ shards failing), and merging on the false-positive watch exit cost:
36
+ - A fix-forward (#540) for the green-up of the same change.
37
+ - A ~16-hour fleet outage on instar-codey when the ABI-heal regression hit
38
+ production.
39
+ - A memory entry (`[feedback_never_merge_on_watch_exit_verify_checks]`)
40
+ warning against the pattern in the future, which is exactly the
41
+ "structure > willpower" antipattern this PR replaces.
42
+
43
+ Per the post-mortem (PR #545), this is lever E: smallest of the remaining
44
+ levers, closes the most embarrassing class. Pure structural enforcement —
45
+ no relying on the agent to remember to check.
46
+
47
+ ## Risk surface
48
+
49
+ - **`gh pr merge --auto` is allowed.** This is the documented async safe
50
+ path — async, fires only when checks pass, idempotent. Allowing it does
51
+ not weaken the gate.
52
+ - **Pattern-bounded.** The command-start anchor (`(^|[;&|(\s])`) prevents
53
+ false-positives on string literals, comments, or commands that happen to
54
+ contain the literal phrase.
55
+ - **No-PR fallback.** If no PR number is in the command line, the gate
56
+ consults `gh pr view --json number` to resolve the current branch's PR.
57
+ If that fails (e.g. not in a git repo, or no PR exists), the gate
58
+ exits silently — allowing the command. This is the documented behavior
59
+ of `gh pr merge` itself when no PR exists; we don't add new failure
60
+ modes.
61
+ - **Skipped-checks tolerance.** `SKIPPED` / `SKIPPING` checks (e.g.
62
+ Contract Tests on non-tagged PRs) are explicitly OK. The most common
63
+ intentionally-skipped case in our pipeline.
64
+ - **Default-on for new and existing agents.** New agents get it via fresh
65
+ init; existing agents get it via PostUpdateMigrator's always-overwrite
66
+ rewrite of `dangerous-command-guard.sh` on every auto-update tick.
67
+
68
+ ## Bug surfaces eliminated
69
+
70
+ - A future "merged on a red CI shard because watch exited 0" incident is
71
+ structurally impossible from the dangerous-command-guard surface.
72
+ - `gh pr merge --admin` with red checks is also blocked (the #539 incident
73
+ shape itself).
74
+ - `gh pr merge` with PENDING checks (e.g. immediately after a push, before
75
+ CI has fired) is blocked — the agent must wait, OR use `--auto`.
76
+
77
+ ## Migration footprint
78
+
79
+ `dangerous-command-guard.sh` is on the always-overwrite list (per
80
+ `PostUpdateMigrator.migrateHooks`). Existing agents pick up the new gate on
81
+ the next auto-update tick — typically within ~30 min of release. No config
82
+ schema change, no per-agent migration.
83
+
84
+ ## Testing
85
+
86
+ - Unit: `tests/unit/dangerous-command-guard-gh-pr-merge-gate.test.ts` — 10
87
+ tests. Two surface checks (both guard writers contain the gate block) +
88
+ eight behavioral checks (spawn the rendered hook with a mocked `gh`
89
+ binary on PATH, verify BLOCK on FAILURE / PENDING, ALLOW on SUCCESS /
90
+ SKIPPED, ALLOW on `--auto`, IGNORE non-`gh pr merge` commands, BLOCK on
91
+ `--admin` with red checks, BLOCK on no-PR-number with red current-branch
92
+ PR).
93
+ - `tests/unit/migration-parity-hooks.test.ts` (from PR #545) still green:
94
+ the new gate landed in BOTH writers in lockstep, so no new gap surfaces.
95
+ - `tsc --noEmit` clean.
96
+
97
+ ## Follow-ups
98
+
99
+ - The other post-mortem levers from PR #545's "What Changed" still pending:
100
+ B (real-world-state fixture tests — biggest, separate conversation), D
101
+ (silent-failure ban lint — small, similar shape to PR #542's
102
+ secret-externalization lint).
@@ -0,0 +1,113 @@
1
+ # Side-effects review — Failure-Learning git reads opt into sourceTreeReadOk
2
+
3
+ ## What changed
4
+
5
+ Three `SafeGitExecutor.readSync` call-sites used by the Failure-Learning Loop
6
+ now pass `sourceTreeReadOk: true`:
7
+
8
+ 1. `src/monitoring/RevertDetector.ts` — default `git` injection (used when the
9
+ constructor caller doesn't supply an override).
10
+ 2. `src/server/AgentServer.ts` — the `FailureAttributionEngine`'s
11
+ `commitTouchedFiles` (uses `git show --name-only`).
12
+ 3. `src/server/AgentServer.ts` — the `CiFailurePoller`'s `resolveRepo` (uses
13
+ `git remote get-url origin`).
14
+
15
+ All three verbs (`log`, `show`, `remote`) are already in
16
+ `SOURCE_TREE_READ_TIER_VERBS`, so the flag legitimately opens the read on the
17
+ instar source tree without weakening the destructive-write protection.
18
+
19
+ ## Why
20
+
21
+ The 2026-05-29 pipeline post-mortem (PR #545) flipped the
22
+ `monitoring.failureLearning.sources.ci: true` and `sources.revert: true`
23
+ flags on Echo. Within an hour of the flip — and continuing for several hours
24
+ after — `logs/server-stderr.log` filled with
25
+
26
+ ```
27
+ [WARN] [revert-detector] SourceTreeGuardError: Refusing to run
28
+ failure-learning:revert-detect against the instar source tree (requested
29
+ dir: /Users/justin/.instar/agents/echo, resolved git root: /Users/justin/
30
+ .instar/agents/echo).
31
+ ```
32
+
33
+ once per detector tick (every ~5 min). `GET /failures/analysis` reported
34
+ `total: 1` (the manually-diagnosed entry I posted that morning) because
35
+ every actual CI/revert ingestion attempt was silently bouncing off the
36
+ guard. The detector's `onError` callback logged a warn and then returned,
37
+ so the ledger never saw the event.
38
+
39
+ `SourceTreeGuard` exists to prevent destructive operations against the
40
+ instar source tree (the 2026-04-22 incident class). The escape hatch
41
+ `SafeGitOptions.sourceTreeReadOk: true` is the documented opt-in for
42
+ legitimate read-tier calls against an agent's own checkout — already used
43
+ by the worktree-manager and the canonical-ref reconciler. The
44
+ Failure-Learning Loop's revert-detector and CI-poller are exactly the
45
+ same shape: they read the agent's own repo to do their job. The fix is
46
+ to opt them into the existing escape hatch, not to weaken the guard.
47
+
48
+ The substrate (`SOURCE_TREE_READ_TIER_VERBS`, `sourceTreeReadOk`,
49
+ `isSourceTreeCheckBypassed`) was already shipped — this PR just wires
50
+ the failure-learning consumers into it.
51
+
52
+ ## Risk surface
53
+
54
+ - **No new destructive-shape exposure.** `sourceTreeReadOk: true` only
55
+ bypasses the source-tree check for verbs in the explicit
56
+ `SOURCE_TREE_READ_TIER_VERBS` set. Destructive verbs still hit the
57
+ guard the same as before. `readSync` itself also fails-closed on any
58
+ non-read shape (via `isReadOnlyShape` + `READONLY_GIT_VERBS`) before
59
+ the source-tree check is even consulted.
60
+ - **No fleet config change.** Default off agents see no behavior change.
61
+ Agents that have already opted in (only Echo today) start capturing
62
+ CI failures + reverts on the next process restart.
63
+ - **Tests** — one unit (4 sub-tests, static introspection of the
64
+ call-shape across RevertDetector + AgentServer + the catch-all
65
+ failure-learning callsite scanner) and one integration (1 sub-test,
66
+ exercises the DEFAULT git invocation against the actual instar
67
+ source tree — the path the existing unit tests entirely mocked,
68
+ which is how the gap shipped silently in the first place). Both
69
+ positive- and negative-verified by removing the flag.
70
+
71
+ ## Bug surfaces eliminated
72
+
73
+ - The failure-learning loop's `revert` source captures real reverts on
74
+ dogfooding agents (Echo).
75
+ - The failure-learning loop's `ci` source can now resolve the GitHub
76
+ repo URL on dogfooding agents (it was silently returning `null` from
77
+ `resolveRepo` and skipping the entire poll).
78
+ - Future failure-learning git reads added against the agent's source
79
+ tree must carry the flag — surfaced at commit time by the catch-all
80
+ test.
81
+
82
+ ## Migration footprint
83
+
84
+ None. This is pure source-code wiring; no config schema change, no
85
+ fleet migration required. Existing agents pick up the fix on next
86
+ process restart (auto-update path).
87
+
88
+ ## Testing
89
+
90
+ - Unit: `tests/unit/failure-learning-source-tree-readok-wiring.test.ts`
91
+ — 4 tests. Per-callsite positive checks + catch-all that scans the
92
+ failure-learning surface. Verified positive + destructive-negative
93
+ (removing the flag from RevertDetector → catch-all test fails as
94
+ expected).
95
+ - Integration: `tests/integration/failure-learning-real-source-tree.test.ts`
96
+ — 1 test. Constructs RevertDetector with NO git override (forces the
97
+ default SafeGitExecutor.readSync path), points `cwd` at the test's own
98
+ repo root (which IS the instar source on every CI shard), asserts the
99
+ default invocation does not throw SourceTreeGuardError and returns a
100
+ real commit-subject string. Verified negative — removing the flag
101
+ makes the test fail with `SourceTreeGuardError`.
102
+
103
+ ## Follow-ups
104
+
105
+ - Meta lesson: the existing RevertDetector + CiFailurePoller unit tests
106
+ mock `git`/`runGh` entirely, which is how this gap shipped silently.
107
+ This is an instance of post-mortem pattern #1 ("tested on fresh state,
108
+ not real-world state"). Post-mortem lever B (real-world-state fixture
109
+ test class) closes the broader pattern; until then, the catch-all
110
+ static-introspection test pins this specific class.
111
+ - Echo's accumulated `[revert-detector] SourceTreeGuardError` warnings
112
+ in `server-stderr.log` will stop appearing after the v1.3.109+
113
+ auto-update lands.
@@ -0,0 +1,65 @@
1
+ # Side-effects review — Mentor loop session spawns with no project MCP
2
+
3
+ **Spec:** `docs/specs/LOOP-SESSION-NO-MCP-SPEC.md`
4
+ **Change:** the mentor autonomous-fix loop spawns its per-cycle Opus session with
5
+ no project MCP servers, so a headless spawn doesn't hang on auth-required remote
6
+ MCP boot. Found by the live dogfood of the loop (#554).
7
+ **Class:** mentor-loop reliability fix (the feature ships dark).
8
+
9
+ ## What changed
10
+
11
+ - **`src/core/frameworkSessionLaunch.ts`** (+1 export) — `claudeHeadlessExtraFlags(opts)`:
12
+ the single pure builder for the claude-code headless flags spliced before `-p`
13
+ (the existing `--allowedTools` scope + the new `--strict-mcp-config --mcp-config
14
+ '{"mcpServers":{}}'` no-MCP spawn). Returns `[]` for non-claude / no options.
15
+ - **`src/core/SessionManager.ts`** — new opt-in `spawnSession({ disableProjectMcp })`;
16
+ the two inline flag-splice blocks (`--allowedTools` + the new MCP-disable) are
17
+ replaced by one call to `claudeHeadlessExtraFlags`.
18
+ - **`src/server/AgentServer.ts`** — `spawnLoopSession` passes `disableProjectMcp: true`.
19
+
20
+ ## Blast radius
21
+
22
+ - **Other spawn callers:** unchanged. `disableProjectMcp` is opt-in (default
23
+ unset). Interactive sessions, mentee-handle spawns, Stage-A, and jobs leave it
24
+ unset ⇒ byte-identical argv to today (project MCP loads as before).
25
+ - **`--allowedTools` behaviour:** preserved exactly — same flags, now built by the
26
+ shared helper instead of an inline splice. Covered by the existing
27
+ framework-launch unit suite (56 tests still green) + the new helper test.
28
+ - **Codex spawns:** unaffected — the helper returns `[]` for non-claude
29
+ frameworks (Codex MCP wiring is separate, via sandbox modes).
30
+ - **MCP for the loop session:** the loop drives the mentee over Telegram (bash +
31
+ relay / a2a HTTP) and ships fixes with built-in tools — it used no MCP, so
32
+ removing it has no functional loss, only removes the boot hazard.
33
+
34
+ ## What could break (and why it doesn't)
35
+
36
+ - **Loop needs an MCP tool?** It doesn't — it uses built-in Bash/Edit/Read/Write
37
+ + git/gh via Bash, and Telegram via the relay script. If a future loop variant
38
+ needs a specific MCP, pass a curated `--mcp-config` rather than the project one.
39
+ - **`--mcp-config` inline-string support?** Verified against the installed claude
40
+ CLI: `--mcp-config '{"mcpServers":{}}'` is accepted and starts zero servers
41
+ (a headless `-p` run that otherwise stalled booted in ~9s).
42
+ - **Splice position?** The helper's flags are spliced before `-p` exactly like the
43
+ prior `--allowedTools` code; argv structure is unchanged otherwise.
44
+
45
+ ## Security
46
+
47
+ No new external input / network / auth / fs surface. The change REMOVES MCP
48
+ servers from one spawn path (strictly fewer external connections). The empty MCP
49
+ config is a constant inline string.
50
+
51
+ ## Migration parity
52
+
53
+ Server-internal session-spawn code — every agent gets it by running the new
54
+ build. No config, schema, hook, or CLAUDE.md change. No PostUpdateMigrator entry.
55
+
56
+ ## Rollback
57
+
58
+ Revert the commit, or stop passing `disableProjectMcp`. No persisted state.
59
+
60
+ ## Tests
61
+
62
+ `tests/unit/claude-headless-extra-flags.test.ts` (+6): every helper branch incl.
63
+ the empty-MCP-config validity. `tests/e2e/mentor-onboarding-lifecycle.test.ts`
64
+ (+1 assertion): the production loop spawn passes `disableProjectMcp: true`.
65
+ Framework-launch regression suite (56 tests) green. `tsc` + `npm run lint` clean.
@@ -0,0 +1,79 @@
1
+ # Side-effects review — Mentor Autonomous-Fix Loop ("just be Echo")
2
+
3
+ **Spec:** `docs/specs/MENTOR-AUTONOMOUS-FIX-LOOP-SPEC.md`
4
+ **Change:** a config-gated GUARDIAN path on the mentor heartbeat that keeps one
5
+ full-tool Opus session alive on the manual dogfooding loop (assign → observe →
6
+ FIX as a fleet PR → report) instead of the haiku observe-and-log pipeline.
7
+ **Class:** mentor capability (off by default; opt-in per agent).
8
+
9
+ ## What changed
10
+
11
+ - **`src/scheduler/MentorAutonomousGuardian.ts`** (new) — pure
12
+ `runAutonomousGuardian(deps)` gate sequence + `buildAutoloopGoal(params)`
13
+ deterministic goal prompt. No I/O; every side-effect injected.
14
+ - **`src/scheduler/MentorOnboardingRunner.ts`** — new `MentorAutonomousFixConfig`
15
+ type + optional `MentorConfig.autonomousFix`; three optional guardian services
16
+ on `MentorRunnerServices`; widened `MentorRunReason` + `sessionName` on
17
+ `MentorRunResult`; `tick()`/`startTick()` route to the guardian when
18
+ `autonomousFix.enabled` (regardless of `mode`); new private `autonomousTick`.
19
+ - **`src/config/ConfigDefaults.ts`** — `mentor.autonomousFix` dark default added
20
+ to `SHARED_DEFAULTS`.
21
+ - **`src/server/AgentServer.ts`** — wired `loopSessionAlive`, `spawnLoopSession`
22
+ (full-tool Opus spawn), `buildAutoloopGoal` into `buildMentorRunner`.
23
+ - **`src/core/PostUpdateMigrator.ts`** — CLAUDE.md mentor section gains the
24
+ autonomous-fix paragraph (new-agent text + a content-sniffed parity paragraph
25
+ for agents that already have the section).
26
+
27
+ ## Blast radius
28
+
29
+ - **Observe-pipeline:** unchanged. The guardian is a separate branch reached only
30
+ when `autonomousFix.enabled`. With the dark default, every existing agent runs
31
+ exactly the prior code path.
32
+ - **`runMentorTick` (pure):** untouched — the guardian is a sibling module, not a
33
+ modification of the tick core.
34
+ - **Config reads:** `readMentorConfigFromDisk` already spreads
35
+ `{...DEFAULT_MENTOR_CONFIG, ...parsed.mentor}`; an absent `autonomousFix` reads
36
+ as `undefined` → disabled. Safe for every config on disk today.
37
+ - **Session spawn:** the loop session uses the normal `spawnSession` path
38
+ (full-tool, like a regular session) — no new spawn machinery. The only new
39
+ behaviour is that, when ENABLED, the heartbeat can start a session; the four
40
+ gates bound that.
41
+ - **Other subsystems / DB schema / HTTP routes:** none added or changed. The
42
+ guardian rides the existing `/mentor/tick` + `/mentor/status` routes.
43
+
44
+ ## What could break (and why it doesn't)
45
+
46
+ - **Spawn-storm of expensive Opus sessions?** The single-instance gate
47
+ (`loopSessionAlive`) makes the heartbeat a no-op while a cycle is running; a
48
+ cycle outlives many heartbeats. Budget + min-interval add two more bounds.
49
+ - **Enabled but no spawner wired?** Surfaces as `reason: 'spawn-failed'` with a
50
+ clear message in `/mentor/status.lastResult.error` — never a silent no-op.
51
+ - **Autonomous code landing untested?** The spawned Echo passes the same husky +
52
+ instar-dev + CI gates as an interactive developer; those are framework-agnostic.
53
+ - **`mode: 'off'` blocking it?** Intentional: the guardian is a distinct path that
54
+ keys on `enabled && autonomousFix.enabled`, so `mode` (which gates only the
55
+ haiku pipeline) does not disable it.
56
+
57
+ ## Security
58
+
59
+ No new external input, network, auth, or fs surface. The loop session is spawned
60
+ through the existing SessionManager with the agent's normal tool grant. Spawning
61
+ is gated behind an opt-in dark flag.
62
+
63
+ ## Migration parity
64
+
65
+ `mentor.autonomousFix` is in `SHARED_DEFAULTS`, so `migrateConfig`'s `applyDefaults`
66
+ deep-merge adds the dark sub-block to existing agents on update
67
+ (existence-checked). CLAUDE.md parity handled in `migrateClaudeMd`. No new job.
68
+
69
+ ## Rollback
70
+
71
+ Set `mentor.autonomousFix.enabled: false` (or remove the block) — instantly back
72
+ to the observe-pipeline. Revert the commit to remove the code; no persisted state,
73
+ schema, or API contract is affected.
74
+
75
+ ## Tests
76
+
77
+ 48 mentor tests green across all three tiers (unit guardian + runner branch,
78
+ integration routes, E2E production-wiring spawn with the Opus model + full tools),
79
+ plus migration-parity unit tests. `tsc` clean.