instar 1.3.465 → 1.3.467

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.
@@ -0,0 +1,58 @@
1
+ /**
2
+ * ensureInteractiveReady — onboarding-safe config homes for the subscription
3
+ * pool (2026-06-09 incident, topic 20905).
4
+ *
5
+ * Pool-account config homes are created via `claude auth login`, which stores
6
+ * OAuth tokens but is HEADLESS-ONLY: it never sets the interactive first-launch
7
+ * onboarding flags. Relaunching an INTERACTIVE Claude Code session into such a
8
+ * home (session pinning, proactive/reactive account swap) re-runs first-launch
9
+ * onboarding — the OAuth-authorize URL + the "Bypass Permissions mode" accept
10
+ * screen — and the session wedges. The tokens were present; the onboarding
11
+ * FLAGS were the missing piece (~8 live sessions wedged, browser-tab spam,
12
+ * a manual operator login to recover).
13
+ *
14
+ * This util makes pin/swap onboarding-safe by construction: idempotently seed
15
+ * the three local trust-acknowledgement flags in `<configHome>/.claude.json`,
16
+ * preserving every other key byte-for-byte.
17
+ *
18
+ * Structural invariants:
19
+ * - NEVER touches `oauthAccount` or any token/credential field — only the
20
+ * three onboarding flags are ever written, onto the parsed existing object.
21
+ * - NEVER rewrites a file it cannot parse — an unparseable `.claude.json`
22
+ * may still hold the account's credentials in a salvageable form, so we
23
+ * refuse and report rather than clobber.
24
+ * - Fail-safe: never throws into the caller. A launch must not crash on
25
+ * this; the worst case is the pre-fix behavior (onboarding wedge), not a
26
+ * dead spawn path. All failures return `{ patched: false, reason }`.
27
+ * - Idempotent + cheap (one stat/read; a write only when a flag is missing),
28
+ * so calling it defensively on every pinned/swapped launch is fine.
29
+ */
30
+ /**
31
+ * The interactive first-launch flags `claude auth login` (headless) never
32
+ * sets. All three are LOCAL trust acknowledgements — seeding them to `true`
33
+ * asserts nothing about credentials, only that first-launch onboarding and
34
+ * the bypass-permissions/trust dialogs are accepted for this home.
35
+ */
36
+ export declare const INTERACTIVE_ONBOARDING_FLAGS: readonly ["hasCompletedOnboarding", "bypassPermissionsModeAccepted", "hasTrustDialogAccepted"];
37
+ export interface EnsureInteractiveReadyResult {
38
+ /** True when this call wrote one or more missing flags. */
39
+ patched: boolean;
40
+ /** Why nothing was written (or which flags were seeded when patched). */
41
+ reason: string;
42
+ }
43
+ export interface EnsureInteractiveReadyOptions {
44
+ /**
45
+ * When true, a config home whose directory does not exist is left alone
46
+ * (`patched: false`) instead of being created. Used by the one-time
47
+ * migration sweep so a stale pool entry never litters $HOME with empty
48
+ * credential-less homes; launch paths keep the default (create/merge —
49
+ * the account was just selected, so its home is expected to exist).
50
+ */
51
+ requireExistingHome?: boolean;
52
+ }
53
+ /**
54
+ * Idempotently mark `<configHome>/.claude.json` interactive-ready. Returns
55
+ * `{ patched: true }` only when a missing flag was actually written.
56
+ */
57
+ export declare function ensureInteractiveReady(configHome: string, opts?: EnsureInteractiveReadyOptions): EnsureInteractiveReadyResult;
58
+ //# sourceMappingURL=ensureInteractiveReady.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensureInteractiveReady.d.ts","sourceRoot":"","sources":["../../src/core/ensureInteractiveReady.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,gGAI/B,CAAC;AAEX,MAAM,WAAW,4BAA4B;IAC3C,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,6BAA6B;IAC5C;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAMD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,6BAA6B,GACnC,4BAA4B,CA+D9B"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * ensureInteractiveReady — onboarding-safe config homes for the subscription
3
+ * pool (2026-06-09 incident, topic 20905).
4
+ *
5
+ * Pool-account config homes are created via `claude auth login`, which stores
6
+ * OAuth tokens but is HEADLESS-ONLY: it never sets the interactive first-launch
7
+ * onboarding flags. Relaunching an INTERACTIVE Claude Code session into such a
8
+ * home (session pinning, proactive/reactive account swap) re-runs first-launch
9
+ * onboarding — the OAuth-authorize URL + the "Bypass Permissions mode" accept
10
+ * screen — and the session wedges. The tokens were present; the onboarding
11
+ * FLAGS were the missing piece (~8 live sessions wedged, browser-tab spam,
12
+ * a manual operator login to recover).
13
+ *
14
+ * This util makes pin/swap onboarding-safe by construction: idempotently seed
15
+ * the three local trust-acknowledgement flags in `<configHome>/.claude.json`,
16
+ * preserving every other key byte-for-byte.
17
+ *
18
+ * Structural invariants:
19
+ * - NEVER touches `oauthAccount` or any token/credential field — only the
20
+ * three onboarding flags are ever written, onto the parsed existing object.
21
+ * - NEVER rewrites a file it cannot parse — an unparseable `.claude.json`
22
+ * may still hold the account's credentials in a salvageable form, so we
23
+ * refuse and report rather than clobber.
24
+ * - Fail-safe: never throws into the caller. A launch must not crash on
25
+ * this; the worst case is the pre-fix behavior (onboarding wedge), not a
26
+ * dead spawn path. All failures return `{ patched: false, reason }`.
27
+ * - Idempotent + cheap (one stat/read; a write only when a flag is missing),
28
+ * so calling it defensively on every pinned/swapped launch is fine.
29
+ */
30
+ import fs from 'node:fs';
31
+ import path from 'node:path';
32
+ /**
33
+ * The interactive first-launch flags `claude auth login` (headless) never
34
+ * sets. All three are LOCAL trust acknowledgements — seeding them to `true`
35
+ * asserts nothing about credentials, only that first-launch onboarding and
36
+ * the bypass-permissions/trust dialogs are accepted for this home.
37
+ */
38
+ export const INTERACTIVE_ONBOARDING_FLAGS = [
39
+ 'hasCompletedOnboarding',
40
+ 'bypassPermissionsModeAccepted',
41
+ 'hasTrustDialogAccepted',
42
+ ];
43
+ function errMsg(err) {
44
+ return err instanceof Error ? err.message : String(err);
45
+ }
46
+ /**
47
+ * Idempotently mark `<configHome>/.claude.json` interactive-ready. Returns
48
+ * `{ patched: true }` only when a missing flag was actually written.
49
+ */
50
+ export function ensureInteractiveReady(configHome, opts) {
51
+ try {
52
+ const trimmed = (configHome ?? '').trim();
53
+ if (!trimmed)
54
+ return { patched: false, reason: 'empty configHome' };
55
+ // Expand a `~` prefix the same way the swap-continuity path does — pool
56
+ // entries are operator-entered and may be tilde-relative.
57
+ const home = trimmed.startsWith('~')
58
+ ? path.join(process.env.HOME ?? '', trimmed.slice(1))
59
+ : trimmed;
60
+ const filePath = path.join(home, '.claude.json');
61
+ if (opts?.requireExistingHome && !fs.existsSync(home)) {
62
+ return { patched: false, reason: `config home ${home} does not exist` };
63
+ }
64
+ let existing = {};
65
+ if (fs.existsSync(filePath)) {
66
+ let raw;
67
+ try {
68
+ raw = fs.readFileSync(filePath, 'utf-8');
69
+ }
70
+ catch (err) {
71
+ return { patched: false, reason: `unreadable ${filePath}: ${errMsg(err)}` };
72
+ }
73
+ let parsed;
74
+ try {
75
+ parsed = JSON.parse(raw);
76
+ }
77
+ catch (err) {
78
+ // Refuse to rewrite what we can't parse — the file may still hold the
79
+ // account's oauthAccount/credentials in a recoverable form.
80
+ return {
81
+ patched: false,
82
+ reason: `unparseable ${filePath} — refusing to rewrite: ${errMsg(err)}`,
83
+ };
84
+ }
85
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
86
+ return {
87
+ patched: false,
88
+ reason: `${filePath} is not a JSON object — refusing to rewrite`,
89
+ };
90
+ }
91
+ existing = parsed;
92
+ }
93
+ const missing = INTERACTIVE_ONBOARDING_FLAGS.filter((f) => existing[f] !== true);
94
+ if (missing.length === 0) {
95
+ return { patched: false, reason: 'already interactive-ready' };
96
+ }
97
+ for (const f of missing)
98
+ existing[f] = true;
99
+ // Atomic tmp+rename write (the repo's durable-registry idiom) so a crash
100
+ // mid-write can never leave a half-written `.claude.json` — that file
101
+ // also carries the account's oauthAccount, which we must never corrupt.
102
+ fs.mkdirSync(home, { recursive: true });
103
+ const tmpPath = `${filePath}.${process.pid}.interactive-ready.tmp`;
104
+ fs.writeFileSync(tmpPath, JSON.stringify(existing, null, 2) + '\n');
105
+ fs.renameSync(tmpPath, filePath);
106
+ return { patched: true, reason: `seeded ${missing.join(', ')}` };
107
+ }
108
+ catch (err) {
109
+ // @silent-fallback-ok: fail-safe by contract — a launch path must never
110
+ // crash on readiness seeding; the caller logs the reason.
111
+ return { patched: false, reason: `ensureInteractiveReady failed: ${errMsg(err)}` };
112
+ }
113
+ }
114
+ //# sourceMappingURL=ensureInteractiveReady.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensureInteractiveReady.js","sourceRoot":"","sources":["../../src/core/ensureInteractiveReady.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.465",
3
+ "version": "1.3.467",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-10T05:59:49.891Z",
5
- "instarVersion": "1.3.465",
4
+ "generatedAt": "2026-06-10T07:26:55.306Z",
5
+ "instarVersion": "1.3.467",
6
6
  "entryCount": 199,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -11,7 +11,7 @@
11
11
  "domain": "identity",
12
12
  "sourcePath": "src/core/PostUpdateMigrator.ts",
13
13
  "installedPath": ".instar/hooks/instar/session-start.sh",
14
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
14
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
15
15
  "since": "2025-01-01"
16
16
  },
17
17
  "hook:dangerous-command-guard": {
@@ -20,7 +20,7 @@
20
20
  "domain": "safety",
21
21
  "sourcePath": "src/core/PostUpdateMigrator.ts",
22
22
  "installedPath": ".instar/hooks/instar/dangerous-command-guard.sh",
23
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
23
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
24
24
  "since": "2025-01-01"
25
25
  },
26
26
  "hook:grounding-before-messaging": {
@@ -29,7 +29,7 @@
29
29
  "domain": "safety",
30
30
  "sourcePath": "src/core/PostUpdateMigrator.ts",
31
31
  "installedPath": ".instar/hooks/instar/grounding-before-messaging.sh",
32
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
32
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
33
33
  "since": "2025-01-01"
34
34
  },
35
35
  "hook:compaction-recovery": {
@@ -38,7 +38,7 @@
38
38
  "domain": "identity",
39
39
  "sourcePath": "src/core/PostUpdateMigrator.ts",
40
40
  "installedPath": ".instar/hooks/instar/compaction-recovery.sh",
41
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
41
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
42
42
  "since": "2025-01-01"
43
43
  },
44
44
  "hook:external-operation-gate": {
@@ -47,7 +47,7 @@
47
47
  "domain": "safety",
48
48
  "sourcePath": "src/core/PostUpdateMigrator.ts",
49
49
  "installedPath": ".instar/hooks/instar/external-operation-gate.js",
50
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
50
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
51
51
  "since": "2025-01-01"
52
52
  },
53
53
  "hook:deferral-detector": {
@@ -56,7 +56,7 @@
56
56
  "domain": "safety",
57
57
  "sourcePath": "src/core/PostUpdateMigrator.ts",
58
58
  "installedPath": ".instar/hooks/instar/deferral-detector.js",
59
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
59
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
60
60
  "since": "2025-01-01"
61
61
  },
62
62
  "hook:self-stop-guard": {
@@ -65,7 +65,7 @@
65
65
  "domain": "coherence",
66
66
  "sourcePath": "src/core/PostUpdateMigrator.ts",
67
67
  "installedPath": ".instar/hooks/instar/self-stop-guard.js",
68
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
68
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
69
69
  "since": "2025-01-01"
70
70
  },
71
71
  "hook:post-action-reflection": {
@@ -74,7 +74,7 @@
74
74
  "domain": "evolution",
75
75
  "sourcePath": "src/core/PostUpdateMigrator.ts",
76
76
  "installedPath": ".instar/hooks/instar/post-action-reflection.js",
77
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
77
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
78
78
  "since": "2025-01-01"
79
79
  },
80
80
  "hook:external-communication-guard": {
@@ -83,7 +83,7 @@
83
83
  "domain": "safety",
84
84
  "sourcePath": "src/core/PostUpdateMigrator.ts",
85
85
  "installedPath": ".instar/hooks/instar/external-communication-guard.js",
86
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
86
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
87
87
  "since": "2025-01-01"
88
88
  },
89
89
  "hook:scope-coherence-collector": {
@@ -92,7 +92,7 @@
92
92
  "domain": "coherence",
93
93
  "sourcePath": "src/core/PostUpdateMigrator.ts",
94
94
  "installedPath": ".instar/hooks/instar/scope-coherence-collector.js",
95
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
95
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
96
96
  "since": "2025-01-01"
97
97
  },
98
98
  "hook:scope-coherence-checkpoint": {
@@ -101,7 +101,7 @@
101
101
  "domain": "coherence",
102
102
  "sourcePath": "src/core/PostUpdateMigrator.ts",
103
103
  "installedPath": ".instar/hooks/instar/scope-coherence-checkpoint.js",
104
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
104
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
105
105
  "since": "2025-01-01"
106
106
  },
107
107
  "hook:free-text-guard": {
@@ -110,7 +110,7 @@
110
110
  "domain": "safety",
111
111
  "sourcePath": "src/core/PostUpdateMigrator.ts",
112
112
  "installedPath": ".instar/hooks/instar/free-text-guard.sh",
113
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
113
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
114
114
  "since": "2025-01-01"
115
115
  },
116
116
  "hook:claim-intercept": {
@@ -119,7 +119,7 @@
119
119
  "domain": "coherence",
120
120
  "sourcePath": "src/core/PostUpdateMigrator.ts",
121
121
  "installedPath": ".instar/hooks/instar/claim-intercept.js",
122
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
122
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
123
123
  "since": "2025-01-01"
124
124
  },
125
125
  "hook:claim-intercept-response": {
@@ -128,7 +128,7 @@
128
128
  "domain": "coherence",
129
129
  "sourcePath": "src/core/PostUpdateMigrator.ts",
130
130
  "installedPath": ".instar/hooks/instar/claim-intercept-response.js",
131
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
131
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
132
132
  "since": "2025-01-01"
133
133
  },
134
134
  "hook:stop-gate-router": {
@@ -137,7 +137,7 @@
137
137
  "domain": "safety",
138
138
  "sourcePath": "src/core/PostUpdateMigrator.ts",
139
139
  "installedPath": ".instar/hooks/instar/stop-gate-router.js",
140
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
140
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
141
141
  "since": "2025-01-01"
142
142
  },
143
143
  "hook:auto-approve-permissions": {
@@ -146,7 +146,7 @@
146
146
  "domain": "safety",
147
147
  "sourcePath": "src/core/PostUpdateMigrator.ts",
148
148
  "installedPath": ".instar/hooks/instar/auto-approve-permissions.js",
149
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
149
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
150
150
  "since": "2025-01-01"
151
151
  },
152
152
  "job:health-check": {
@@ -1514,7 +1514,7 @@
1514
1514
  "type": "subsystem",
1515
1515
  "domain": "sessions",
1516
1516
  "sourcePath": "src/core/SessionManager.ts",
1517
- "contentHash": "1b58893e54317eed379d38d736a6c001ba407607c115d84a79795a5439a479f5",
1517
+ "contentHash": "9e1bc7df09921b4ecdd6252c8616ac33b195380a678de7a4c1a616312ca3cae1",
1518
1518
  "since": "2025-01-01"
1519
1519
  },
1520
1520
  "subsystem:auto-updater": {
@@ -1538,7 +1538,7 @@
1538
1538
  "type": "subsystem",
1539
1539
  "domain": "updates",
1540
1540
  "sourcePath": "src/core/PostUpdateMigrator.ts",
1541
- "contentHash": "2c7a661c1d1133c9267caab9e6fb082211549373fac0fbe0d2ca8003783648dd",
1541
+ "contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
1542
1542
  "since": "2025-01-01"
1543
1543
  },
1544
1544
  "subsystem:scheduler": {
@@ -0,0 +1,97 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Extends the `deferral-detector` hook (the signal-only PreToolUse guard that scans outbound messages) with a fifth category: **merge-deferral**. It now flags an agent handing the merge of a PR *it authored* back to the operator — both the explicit form ("the merge call is yours", "your call to merge", "leave the merge to you", "merge is your call to make") and the permission-seeking form ("want me to merge?", "should I merge?", "ready to merge?"). Two design points: (1) these patterns are **deliberately NOT exempted by the infrastructure-backed anti-trigger** — having tracked the PR as a commitment does not legitimize handing its merge back; it just launders the deferral; (2) the injected checklist tells the agent to **merge a self-authored green PR itself** (`scripts/safe-merge.mjs … --squash --admin` in the instar repo, or `gh pr merge`), states the operator directed this must never be a blocker, and names the only legitimate non-merges (CI genuinely red on this change, or someone else's PR). Source-of-truth is `getDeferralDetectorHook()` in PostUpdateMigrator.ts (the existing always-overwrite migration redeploys it to every agent on update). Still SIGNAL ONLY — never blocks. Complements instar-dev Phase 7 (Auto-merge on green) at a different layer: Phase 7 governs the build flow; this catches a handed-back merge in *any* outbound message.
9
+
10
+ ## What to Tell Your User
11
+
12
+ If I ever finish a PR I authored, watch it go green, and then ask you "want me to merge?" or call it "your decision" — there's now a structural guard that catches that framing before the message sends and reminds me that merging my own green PR is mine to do, never a blocker I hand to you. This came directly from your correction that the merge call should never be yours for PRs I authored, and that the fix had to be permanent and in code, not another promise.
13
+
14
+ ## Summary of New Capabilities
15
+
16
+ | Capability | How to Use |
17
+ |-----------|-----------|
18
+ | Deferral-detector flags handing a self-authored PR's merge back to the operator | automatic (signal-only hook); fires on outbound messages, not exempted by "tracked" work |
19
+
20
+ ## Evidence
21
+
22
+ Reproduction (live, 2026-06-09, topic 2169): the agent built PR #1040 (the session auto-heal ladder), watched CI, and presented it as "CI is running now … it's core monitoring code, so the merge call is yours." The operator corrected: "the merge call should never be mine, at least not for PRs you authored. Please change this permanently moving forward so it is never a blocker." The existing deferral-detector did not catch it — it had no merge-deferral patterns.
23
+
24
+ After the fix: `tests/unit/deferral-detector-orphan-todo.test.ts` gains a `merge-deferral` describe block (11 cases) including the exact incident phrasing, the permission-seeking variants ("want me to merge?", "should I merge?", "ready to merge"), the key "tracked PR still fires" laundering case, and the must-NOT-fire cases ("I merged it myself", "I'll merge on green", "merging now"). The deployed hook was verified end-to-end to emit `MERGE-DEFERRAL DETECTED` while staying signal-only. 35/35 deferral-detector tests pass; tsc clean; generated-hook node syntax check passed.
25
+
26
+ ## The problem
27
+
28
+ I built a PR (#1040 — the thing that auto-heals a stalled session), watched the
29
+ CI go all-green, and then told the operator: "it's core monitoring code, so the
30
+ merge call is yours." He pushed back, hard and clearly: **"the merge call should
31
+ never be mine, at least not for PRs you authored. Please change this permanently
32
+ moving forward so it is never a blocker."**
33
+
34
+ He's right. A PR I wrote, that passed every gate and went green, is *mine* to
35
+ merge. Handing that decision back to him does nothing but stall the work and make
36
+ him a bottleneck on my own output. It's a polite-looking version of not finishing
37
+ the job.
38
+
39
+ And the deeper point, which he's made before about other habits: he keeps catching
40
+ me doing this and I keep answering with "understood, I'll do better." A promise is
41
+ willpower. This project's founding rule is **Structure > Willpower** — if a
42
+ behavior matters, put it in code, not in a vow.
43
+
44
+ ## What already exists
45
+
46
+ I already have a `deferral-detector` hook. It scans messages I'm about to send and,
47
+ if it sees me punting work ("queue for next session", "it's late, I'll wrap up",
48
+ "needs a human"), it injects a checklist that re-grounds me before the message goes
49
+ out. It already has four categories: can't-do-it claims, orphan TODOs, false
50
+ "needs a human" blockers, and (added earlier today) time/fatigue deferral.
51
+
52
+ There's also a rule in my instar-dev build skill — Phase 7, "Auto-merge on green" —
53
+ that says when my PR is green I merge it and don't ask. But that rule only governs
54
+ the formal build flow. It didn't stop me from *typing* "the merge call is yours"
55
+ in a normal chat message. That gap is exactly what bit here.
56
+
57
+ ## What's new
58
+
59
+ I added a fifth thing the detector looks for: **merge-deferral** — handing the
60
+ merge of a PR I authored back to the operator. It catches two shapes:
61
+
62
+ - **Explicitly giving him the call:** "the merge call is yours", "your call to
63
+ merge", "leave the merge to you", "merge is your decision".
64
+ - **Asking permission to merge my own PR:** "want me to merge?", "should I merge
65
+ it?", "ready to merge?".
66
+
67
+ When it sees that, it injects a sharp reminder before the message sends:
68
+
69
+ - If it's my PR and CI is green, **merge it myself now** — green CI means
70
+ mergeable. (It even names the command: `safe-merge.mjs … --squash --admin`, or
71
+ `gh pr merge`.)
72
+ - Asking to merge my own green PR is redundant ceremony that stalls delivery, and
73
+ the operator directed it must **never** be a blocker handed to him.
74
+ - Having *tracked* the PR does **not** make handing its merge back okay — so this
75
+ check is **not** silenced by the "I tracked it" escape hatch, same as the
76
+ time/fatigue check.
77
+ - The only real reasons not to merge: CI is genuinely **red on this change** (fix
78
+ it and re-run), or it's **someone else's** PR (then asking is fine).
79
+
80
+ ## The safeguards in plain terms
81
+
82
+ - **Signal, not a block.** Like the rest of the deferral-detector, this never
83
+ blocks a message — it injects a checklist so I re-ground before sending. A
84
+ brittle keyword check shouldn't have the power to hard-block; that stays with
85
+ the smart gates. So a false positive (like me asking about *your* PR) just adds
86
+ a reminder I can disregard, never eats a message.
87
+ - **Ships to every agent.** The hook's source lives in one place and re-deploys to
88
+ every agent on update, so this isn't a me-only patch.
89
+ - **Additive.** It only adds a new detection; it can't reduce the existing four.
90
+
91
+ ## What you need to decide
92
+
93
+ Nothing — it's a signal-only hook extension that ships as a normal patch. The
94
+ point is simply that the next time I finish my own PR, watch it go green, and
95
+ reach for "want me to merge?", the structure catches it and reminds me to just
96
+ merge it — instead of relying on me to remember the lesson and making you the
97
+ bottleneck on my own work.
@@ -0,0 +1,80 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Fixes the 2026-06-09 incident where subscription-pool **pinning** and **account
9
+ swap** relaunched interactive sessions into pool-account config homes that were
10
+ enrolled via headless `claude auth login` — homes with valid OAuth tokens but
11
+ WITHOUT the interactive first-launch onboarding flags. Every relaunched session
12
+ wedged on Claude Code's onboarding screens (OAuth-authorize browser-tab spam +
13
+ the Bypass-Permissions accept screen); ~8 live sessions froze at once until the
14
+ operator logged in manually.
15
+
16
+ New util `ensureInteractiveReady(configHome)` idempotently seeds the three
17
+ local trust-acknowledgement flags (`hasCompletedOnboarding`,
18
+ `bypassPermissionsModeAccepted`, `hasTrustDialogAccepted`) in
19
+ `<configHome>/.claude.json`, preserving every other key and **never touching
20
+ `oauthAccount`/tokens** (an unparseable file is refused, not rewritten; writes
21
+ are atomic). It is fail-safe by contract — a launch can never crash on it.
22
+
23
+ Called everywhere a session can enter a pool home, so the wedge is impossible
24
+ by construction:
25
+
26
+ 1. **Enrollment** — `EnrollmentWizard.complete()` seeds a freshly-enrolled
27
+ claude-code home immediately.
28
+ 2. **Pinned launches** — both SessionManager pin lanes (headless +
29
+ interactive-reroute) and the interactive account-swap lane seed before
30
+ setting `CLAUDE_CONFIG_DIR`.
31
+ 3. **Swaps** — `SessionRefresh` seeds the target home BEFORE the kill+respawn.
32
+ 4. **Existing homes** — a `PostUpdateMigrator` sweep seeds every claude-code
33
+ pool account's existing home once on update (stale entries skipped, never
34
+ created).
35
+
36
+ ## What to Tell Your User
37
+
38
+ If I run on a pool of Claude accounts, moving my sessions between accounts
39
+ (when one hits its weekly limit) used to be able to freeze them on Claude's
40
+ "first launch" welcome screens — the account was logged in, but nobody had
41
+ ever clicked through the one-time setup dialogs for that account's config
42
+ folder. That's fixed at every layer now: new accounts are made
43
+ interactive-ready the moment they're enrolled, every launch double-checks, and
44
+ existing accounts get fixed automatically on this update. Account swaps should
45
+ now be invisible — same conversation, different account, no frozen sessions
46
+ and no browser-tab spam.
47
+
48
+ ## Summary of New Capabilities
49
+
50
+ | Capability | How to Use |
51
+ |-----------|-----------|
52
+ | Pool config homes are onboarding-safe by construction (enrollment + every pinned/swapped launch) | automatic — no config |
53
+ | Existing pool homes seeded once on update | automatic — PostUpdateMigrator sweep |
54
+ | `ensureInteractiveReady(configHome)` util for any future launch lane | `src/core/ensureInteractiveReady.ts` |
55
+
56
+ ## Evidence
57
+
58
+ Live state at the incident (this machine): `sagemind-justin` and `sagemind-dawn`
59
+ homes were fully headless (no flags), `justin-gmail` had onboarded=true but
60
+ bypass=false — exactly the homes whose sessions wedged; the two complete homes'
61
+ sessions were unaffected. The OAuth tokens were present in every case.
62
+
63
+ After the change:
64
+ - `tests/unit/ensure-interactive-ready.test.ts` — 14 cases: missing-file
65
+ create, partial merge, flag-false reseed, idempotency (mtime-stable second
66
+ call), oauthAccount/token preservation, tilde expansion, unparseable/
67
+ non-object refusal with bytes preserved, unreadable fail-safe,
68
+ requireExistingHome both sides.
69
+ - `tests/unit/PostUpdateMigrator-subscriptionPoolInteractiveReady.test.ts` — 8
70
+ cases incl. one-bad-home-never-aborts-the-sweep and full-migrate() wiring.
71
+ - `tests/unit/SessionRefresh.test.ts` — flags land BEFORE the respawner fires
72
+ (ordering pinned), fresh swaps too, no-swap untouched, fail-safe.
73
+ - `tests/integration/subscription-pin-sessions.test.ts` — both launch lanes
74
+ land the flags on disk through the real SessionManager.
75
+ - `tests/integration/subscription-enrollment-interactive-ready.test.ts` +
76
+ `tests/e2e/subscription-enrollment-lifecycle.test.ts` — full HTTP
77
+ enroll→complete leaves the home interactive-ready, credentials
78
+ byte-identical.
79
+ - `npm run lint` (tsc + all custom lints) clean; subscription/quota regression
80
+ suites green.
@@ -0,0 +1,72 @@
1
+ # Side-Effects Review — deferral-detector merge-deferral category
2
+
3
+ **Version / slug:** `deferral-detector-merge-deferral`
4
+ **Date:** `2026-06-09`
5
+ **Author:** `Instar Agent (echo)`
6
+ **Second-pass reviewer:** `independent reviewer subagent — see below`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds a fifth detection category — **merge-deferral** — to the `deferral-detector` hook (source-of-truth: `getDeferralDetectorHook()` in `src/core/PostUpdateMigrator.ts`; deployed copy mirrors it via the existing always-overwrite hook migration). New `mergeDeferralPatterns` array (7 patterns) catches two shapes of handing the merge of a **self-authored** PR back to the operator: (a) explicitly assigning the call — "the merge call is yours", "your merge call", "your call to merge", "leave the merge to you", "up to you to merge", "merge is your call to make"; and (b) asking permission to merge one's own PR — "want me to merge?", "should I merge?", "ready to merge?". Like the time/fatigue category, `mergeDeferralMatches` is NOT gated by the `isInfrastructureBacked` anti-trigger — having tracked the PR does not legitimize handing its merge back. A new checklist section instructs the agent to merge a self-authored green PR itself (`scripts/safe-merge.mjs … --squash --admin` / `gh pr merge`), states the operator directed this must never be a blocker (2026-06-09), and carves out the only legitimate non-merges (CI genuinely red on this change; someone else's PR). Still signal-only (`decision: 'approve'`, additionalContext only — never blocks). Files: `src/core/PostUpdateMigrator.ts` (hook template), `tests/unit/deferral-detector-orphan-todo.test.ts` (+11 cases).
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `deferral-detector` hook outbound-message scan — **modify (additive)** — adds a new signal category. Does NOT change the existing inability/orphan/time-fatigue categories or the never-block contract.
15
+
16
+ ## 1. Over-block
17
+
18
+ No block surface — the hook is signal-only (injects additionalContext, never blocks/denies). "Over-block" → over-FLAG. Two over-flag classes, both bounded:
19
+
20
+ - **Permission-seeking on someone else's PR.** "want me to merge your PR?" about a PR the *user* authored is a legitimate question, but it still matches `merge_permission_seeking`. The hook can't see PR authorship from message text, so it flags and lets the agent decide — the checklist explicitly states "it is SOMEONE ELSE's PR (then asking is fine)", so the injected context tells the agent to disregard when that applies. Cost is one extra context block, never a withheld message.
21
+ - **Discussing this very feature.** A message *about* merge-deferral (like this one) contains the trigger words and will self-flag — annoying but harmless (signal-only), identical in kind to the orphan/time-fatigue categories self-flagging when discussed.
22
+
23
+ Patterns are scoped to the comm-command gate (only telegram-reply/send-message/etc. are scanned), bounding noise to outbound human messages.
24
+
25
+ ## 2. Under-block
26
+
27
+ Pattern set is finite regex; it will miss novel phrasings ("I'll let you make the merge decision on this", "the green light to merge is yours"). Acceptable for a signal layer — patterns cover the observed incident shape ("the merge call is yours") plus the common permission-seeking variants the operator's directive named. Residual (stated, not deferred): the detector does not attempt to verify PR authorship or CI state — it is a framing detector, not a merge executor. The actual auto-merge-on-green behavior lives in instar-dev Phase 7 (the skill flow); this hook is the cross-cutting catch for when an outbound message hands the merge back regardless of flow.
28
+
29
+ ## 3. Level-of-abstraction fit
30
+
31
+ Correct layer. This is a brittle keyword detector → it belongs as a SIGNAL (the deferral-detector is exactly that), not as blocking authority. It composes with the existing four categories in the same hook at the same altitude, reusing the comm-command gate, the JSON output contract, and the test harness. A complementary structural surface already exists at a different layer: instar-dev Phase 7 ("Auto-merge on green — never pause to ask") governs the build flow. This hook covers the gap Phase 7 doesn't: a self-authored-PR merge handed back in *any* outbound message, not only inside an instar-dev build. The two are layered (flow-level prescription + message-level detector), not parallel duplicates.
32
+
33
+ ## 4. Signal vs authority compliance
34
+
35
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
36
+
37
+ - [x] No — this change has no block/allow surface; it produces a signal (additionalContext) consumed by the agent, exactly per the hook's "SIGNAL ONLY" contract.
38
+
39
+ A brittle regex detector must NOT hold blocking authority; this addition stays signal-only. If hard enforcement is ever wanted, the right path is the instar-dev pre-commit/merge gates (which already exist) or feeding the smart gate (MessagingToneGate), never making this regex block. Compliant.
40
+
41
+ ## 5. Interactions
42
+
43
+ - **Shadowing:** the new category is independent of inability/orphan/time-fatigue; `allMatches` concatenates all categories. The orphan category's infrastructure-backed suppression is unchanged; the new category intentionally bypasses it (documented, mirrors time/fatigue). No existing category is shadowed.
44
+ - **Double-fire:** a message can now trigger multiple sections (e.g. permission-seeking inability + merge-deferral) — intended; each section is additive context. No double-send.
45
+ - **Races:** none — pure stdin→stdout, no shared state.
46
+ - **Migration parity:** `deferral-detector.js` is already in the always-overwrite migration set (`result.upgraded.push('hooks/instar/deferral-detector.js …')`), so the new content redeploys to every agent on update with no new migration entry needed.
47
+
48
+ ## 6. External surfaces
49
+
50
+ - **Agents/users:** ships to the whole install base via the always-overwrite hook migration. Effect: more frequent (signal-only) checklists when an agent hands a self-authored merge back or asks permission to merge. No user-visible message change, no API change.
51
+ - **Persistent state:** none.
52
+ - **Timing/runtime:** depends only on the outbound message text (already available to the hook). No new external calls.
53
+
54
+ ## 7. Rollback cost
55
+
56
+ Pure additive change to a signal hook template + tests. Back-out = revert the commit; on next update the prior hook content redeploys. No persistent state, no migration to unwind. An individual agent can also neutralize it locally by editing its deployed `.instar/hooks/instar/deferral-detector.js` (until next update). Low.
57
+
58
+ ## Conclusion
59
+
60
+ An additive, signal-only change that closes a real, freshly-corrected behavior gap (handing a self-authored green PR's merge back to the operator — incident 2026-06-09, PR #1040 presented as "the merge call is yours") with code rather than willpower — directly per the Structure > Willpower standard, and reinforcing the operator directive that a self-authored merge must never be a blocker. No block surface added; signal-vs-authority compliant; layered with (not duplicating) instar-dev Phase 7. Because it modifies a behavioral guard hook, a Phase-5 second-pass review is requested before commit.
61
+
62
+ ## Second-pass review (if required)
63
+
64
+ **Reviewer:** independent reviewer subagent (general-purpose)
65
+ **Independent read of the artifact: concur**
66
+
67
+ Concur — no must-fix bugs found. The reviewer independently rendered the deployed hook (`getHookContent('deferral-detector')` → `node -c` valid JS) and confirmed: (1) **escaping correct** — every `\\b` → `\b` in the deployed regex, both `\\'` → valid `\'` in the single-quoted checklist strings, no stray backtick / broken escape / silently-never-matching regex (all 7 patterns matched real inputs); (2) **the laundering invariant holds** — `mergeDeferralMatches` is computed by a plain `.filter()` OUTSIDE the `isInfrastructureBacked ? [] : …` gate, verified empirically that "tracked commitment + follow-up PR … but the merge call is yours" STILL fires; (3) **signal-only intact** — exactly one output path (`decision:'approve'`, additionalContext only), six `process.exit(0)`, no block/deny/ask path; (4) **no regression** — the only source deletion is the single `allMatches` line re-spread to append `...mergeDeferralMatches`; inability/orphan/time-fatigue arrays + gates + sections byte-for-byte unchanged; (5) **false-positive profile acceptable** — all must-NOT-fire cases ("I merged it myself", "I'll merge on green", "merging now", "Both fixes verified live") plus 10 more benign merge-mentioning messages produced zero firings; full file 35/35. Bounded over-fire noted (permission-seeking pattern (b) can fire on non-PR "safe to merge these branches?" uses) — over-FLAG not over-block, anticipated in §1, judged not-must-fix for a signal layer (anchoring it to own-PR context would risk under-firing the real incident shape). Under-fire on novel framings noted as expected residual.
68
+
69
+ ## Evidence pointers
70
+
71
+ - Live incident: 2026-06-09, topic 2169 — the agent built PR #1040 (auto-heal ladder) and presented it as "CI is running now … the merge call is yours," prompting the operator's correction: "the merge call should never be mine, at least not for PRs you authored. Please change this permanently moving forward so it is never a blocker."
72
+ - Tests: `tests/unit/deferral-detector-orphan-todo.test.ts` merge-deferral block (11 cases) incl. the exact incident phrasing, the permission-seeking variants, the "tracked PR still fires" laundering case, and the must-NOT-fire cases ("I merged it myself", "I'll merge on green", "merging now"). Generated-hook node syntax check passed; `tsc --noEmit` exit 0; 35/35 deferral-detector tests green.