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.
- package/dist/commands/init.js +48 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +10 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +67 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +11 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +18 -15
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/frameworkSessionLaunch.d.ts +20 -0
- package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
- package/dist/core/frameworkSessionLaunch.js +27 -0
- package/dist/core/frameworkSessionLaunch.js.map +1 -1
- package/dist/monitoring/RevertDetector.d.ts.map +1 -1
- package/dist/monitoring/RevertDetector.js +12 -1
- package/dist/monitoring/RevertDetector.js.map +1 -1
- package/dist/paste/PasteManager.d.ts.map +1 -1
- package/dist/paste/PasteManager.js +13 -6
- package/dist/paste/PasteManager.js.map +1 -1
- package/dist/scheduler/MentorAutonomousGuardian.d.ts +91 -0
- package/dist/scheduler/MentorAutonomousGuardian.d.ts.map +1 -0
- package/dist/scheduler/MentorAutonomousGuardian.js +88 -0
- package/dist/scheduler/MentorAutonomousGuardian.js.map +1 -0
- package/dist/scheduler/MentorOnboardingRunner.d.ts +61 -2
- package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
- package/dist/scheduler/MentorOnboardingRunner.js +48 -3
- package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +63 -2
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +2 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/upgrades/1.3.109.md +80 -0
- package/upgrades/1.3.110.md +55 -0
- package/upgrades/NEXT.eli16.md +60 -63
- package/upgrades/side-effects/dangerous-command-guard-gh-pr-merge-gate.md +102 -0
- package/upgrades/side-effects/failure-learning-source-tree-readok.md +113 -0
- package/upgrades/side-effects/loop-session-no-mcp.md +65 -0
- package/upgrades/side-effects/mentor-autonomous-fix-loop.md +79 -0
- 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`.
|
package/upgrades/NEXT.eli16.md
CHANGED
|
@@ -2,81 +2,78 @@
|
|
|
2
2
|
|
|
3
3
|
## What this change is
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
- A
|
|
52
|
-
|
|
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.
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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.
|