instar 1.3.466 → 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.
- package/dist/core/EnrollmentWizard.d.ts +18 -1
- package/dist/core/EnrollmentWizard.d.ts.map +1 -1
- package/dist/core/EnrollmentWizard.js +22 -2
- package/dist/core/EnrollmentWizard.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +20 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +53 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/ProactiveSwapMonitor.d.ts.map +1 -1
- package/dist/core/ProactiveSwapMonitor.js +7 -0
- package/dist/core/ProactiveSwapMonitor.js.map +1 -1
- package/dist/core/SessionManager.d.ts +10 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +28 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SessionRefresh.d.ts.map +1 -1
- package/dist/core/SessionRefresh.js +13 -0
- package/dist/core/SessionRefresh.js.map +1 -1
- package/dist/core/ensureInteractiveReady.d.ts +58 -0
- package/dist/core/ensureInteractiveReady.d.ts.map +1 -0
- package/dist/core/ensureInteractiveReady.js +114 -0
- package/dist/core/ensureInteractiveReady.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +20 -20
- package/upgrades/1.3.467.md +80 -0
- package/upgrades/side-effects/onboarding-safe-pinswap.md +49 -0
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG;IAC1C,wBAAwB;IACxB,+BAA+B;IAC/B,wBAAwB;CAChB,CAAC;AAoBX,SAAS,MAAM,CAAC,GAAY;IAC1B,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAkB,EAClB,IAAoC;IAEpC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1C,IAAI,CAAC,OAAO;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAEpE,wEAAwE;QACxE,0DAA0D;QAC1D,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAClC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACrD,CAAC,CAAC,OAAO,CAAC;QACZ,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QAEjD,IAAI,IAAI,EAAE,mBAAmB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,IAAI,iBAAiB,EAAE,CAAC;QAC1E,CAAC;QAED,IAAI,QAAQ,GAA4B,EAAE,CAAC;QAC3C,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,IAAI,GAAW,CAAC;YAChB,IAAI,CAAC;gBACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC3C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,QAAQ,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YAC9E,CAAC;YACD,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,sEAAsE;gBACtE,4DAA4D;gBAC5D,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,eAAe,QAAQ,2BAA2B,MAAM,CAAC,GAAG,CAAC,EAAE;iBACxE,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnE,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,GAAG,QAAQ,6CAA6C;iBACjE,CAAC;YACJ,CAAC;YACD,QAAQ,GAAG,MAAiC,CAAC;QAC/C,CAAC;QAED,MAAM,OAAO,GAAG,4BAA4B,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QACjF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;QACjE,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QAE5C,yEAAyE;QACzE,sEAAsE;QACtE,wEAAwE;QACxE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,wBAAwB,CAAC;QACnE,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACpE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACnE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wEAAwE;QACxE,0DAA0D;QAC1D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IACrF,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-10T07:
|
|
5
|
-
"instarVersion": "1.3.
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
1541
|
+
"contentHash": "9f5d958c0cc5a68d7c4d078e10441756637da7dca7139ed48c276079654a5a3e",
|
|
1542
1542
|
"since": "2025-01-01"
|
|
1543
1543
|
},
|
|
1544
1544
|
"subsystem:scheduler": {
|
|
@@ -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,49 @@
|
|
|
1
|
+
# Side-Effects Review — onboarding-safe config homes for subscription pin/swap
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `onboarding-safe-pinswap`
|
|
4
|
+
**Date:** `2026-06-09`
|
|
5
|
+
**Author:** `echo (Fable 5 build session)`
|
|
6
|
+
**Second-pass reviewer:** `self-review under the Tier-1 lite lane; every write path to .claude.json walked below`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
2026-06-09 incident (topic 20905): pin/swap relaunched ~8 interactive sessions into pool-account config homes created by headless `claude auth login` — tokens present, interactive first-launch flags absent — so every one wedged on the onboarding screens. New util `ensureInteractiveReady(configHome)` idempotently seeds `hasCompletedOnboarding` / `bypassPermissionsModeAccepted` / `hasTrustDialogAccepted` in `<configHome>/.claude.json`, called at enrollment completion (EnrollmentWizard), defensively at every pinned/swapped launch (SessionManager headless-pin, interactive-reroute-pin, interactive configHome lanes; SessionRefresh before the swap respawn), and once for existing homes via a PostUpdateMigrator sweep.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `src/core/ensureInteractiveReady.ts` — NEW — the only writer. Sets exactly the three flags; preserves every other key; refuses unparseable/non-object files (never clobbers a file that may hold salvageable credentials); atomic tmp+rename write; never throws (fail-safe contract); `requireExistingHome` gate for the migration sweep.
|
|
15
|
+
- `SessionManager.ensurePinnedHomeInteractiveReady` — NEW private helper — log-and-continue on failure; called in the headless pin lane, the interactive-reroute pin lane, and `spawnInteractiveSession` when `options.configHome` is set (claude-code only).
|
|
16
|
+
- `SessionRefresh.refreshSession` — seeds the target home whenever `accountSwap.configHome` is present (fresh and resume swaps both relaunch interactively), BEFORE the respawner fires.
|
|
17
|
+
- `EnrollmentWizard.complete()` — seeds for claude-code logins with a configHome; injectable `ensureReady` seam (default = real util). A seeding failure never blocks completion.
|
|
18
|
+
- `PostUpdateMigrator.migrateSubscriptionPoolInteractiveReady` — one-time sweep over `SubscriptionPool.list()` claude-code accounts; `requireExistingHome:true` so a stale registry entry never litters $HOME; per-home failures reported in `result.errors`, never abort the sweep.
|
|
19
|
+
- `ProactiveSwapMonitor` — comment-only TODO for active-session-only gating (secondary incident finding, deferred to a follow-up).
|
|
20
|
+
|
|
21
|
+
## Could this corrupt credentials? (the load-bearing question)
|
|
22
|
+
|
|
23
|
+
The util never touches `oauthAccount` or any token field — it sets three booleans on the parsed object and writes the rest back. Unparseable or non-object files are REFUSED, not rewritten (bytes preserved exactly — pinned by unit tests). Writes are atomic (tmp+rename) so a crash mid-write cannot truncate `.claude.json`. The Monroe homes (`~/.claude`, `~/.claude-monroe`) are not in any subscription pool registry and no new code path enumerates $HOME — only registry/launch-provided configHome values are ever touched.
|
|
24
|
+
|
|
25
|
+
## Could the seeding itself break a launch?
|
|
26
|
+
|
|
27
|
+
No — fail-safe by contract: every failure lane returns `{patched:false, reason}`; callers log and proceed. Worst case is byte-identical to the pre-fix behavior (the onboarding wedge), never a dead spawn/refresh path. Pinned-spawn behavior when the resolver returns null, and refreshes without an account swap, are unchanged (covered by existing + new tests).
|
|
28
|
+
|
|
29
|
+
## Framework generality
|
|
30
|
+
|
|
31
|
+
Seeding is gated to claude-code everywhere: the SessionManager lanes only run for claude-code launches, EnrollmentWizard checks `login.framework === 'claude-code'`, and the migrator filters `framework === 'claude-code'`. codex-cli / gemini-cli / pi-cli homes are never touched — `.claude.json` onboarding flags are a Claude Code concept by construction.
|
|
32
|
+
|
|
33
|
+
## Over-permit
|
|
34
|
+
|
|
35
|
+
The three flags are local trust acknowledgements (the operator manually accepted the same dialogs during incident recovery — this reproduces that state for pool homes). `bypassPermissionsModeAccepted` does not ENABLE bypass mode; it records that the mode's accept-screen was answered, which is precisely what an unattended relaunch cannot do itself. No trust scope widens beyond what every pre-incident pool home already required to function.
|
|
36
|
+
|
|
37
|
+
## Migration parity
|
|
38
|
+
|
|
39
|
+
Handled IN this change: existing agents get the sweep via `PostUpdateMigrator.migrate()` on update; new enrollments are seeded at completion; every launch re-ensures defensively (triple coverage). No CLAUDE.md/template surface changes — the feature is structural (Structure > Willpower), not agent-invoked.
|
|
40
|
+
|
|
41
|
+
## Token/cost impact
|
|
42
|
+
|
|
43
|
+
None. One stat + one small JSON read per pinned/swapped launch (write only when flags are missing — i.e., at most once per home). No LLM calls, no new polling.
|
|
44
|
+
|
|
45
|
+
## Test coverage
|
|
46
|
+
|
|
47
|
+
- Unit: `tests/unit/ensure-interactive-ready.test.ts` (14 — both sides of every boundary), `PostUpdateMigrator-subscriptionPoolInteractiveReady.test.ts` (8), `SessionRefresh.test.ts` (+4 ordering/fail-safe), `enrollment-wizard.test.ts` (+4 seam/wiring).
|
|
48
|
+
- Integration: `subscription-pin-sessions.test.ts` (+2 — both launch lanes land the flags on disk), `subscription-enrollment-interactive-ready.test.ts` (2 — full HTTP enroll→complete seeds flags, codex untouched).
|
|
49
|
+
- E2E: `subscription-enrollment-lifecycle.test.ts` (+1 feature-alive — real server, headless-state home becomes interactive-ready with credentials byte-identical).
|