instar 1.3.252 → 1.3.254

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.
@@ -32,6 +32,15 @@ export interface CloudflareQuickProviderOptions {
32
32
  /** Start timeout in milliseconds (default: 30_000). */
33
33
  startTimeoutMs?: number;
34
34
  }
35
+ /**
36
+ * Classify a cloudflared quick-tunnel failure into a fixed-substring reason the
37
+ * TunnelManager parses to a ProviderFailureReason. Pure + exported so the
38
+ * decision boundary (esp. the Cloudflare 429/1015 rate-limit detection) is
39
+ * unit-testable. Depends on the caller having captured cloudflared's real stderr
40
+ * (the 'stderr' event) into `stderr` — otherwise the rate-limit text never
41
+ * reaches the haystack and the classification falls through to generic.
42
+ */
43
+ export declare function classifyQuickTunnelError(msg: string, stderr: string): Error;
35
44
  export declare class CloudflareQuickProvider implements TunnelProvider {
36
45
  readonly name: ProviderName;
37
46
  readonly tier: ProviderTier;
@@ -1 +1 @@
1
- {"version":3,"file":"CloudflareQuickProvider.d.ts","sourceRoot":"","sources":["../../src/tunnel/CloudflareQuickProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACb,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,8BAA8B;IAC7C,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,uBAAwB,YAAW,cAAc;IAC5D,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAsB;IACjD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAK;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,IAAI,EAAE,8BAA8B;IAM1C,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAQrC,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;YASzC,UAAU;IA6FxB,OAAO,CAAC,aAAa;YAiBP,UAAU;CAmCzB"}
1
+ {"version":3,"file":"CloudflareQuickProvider.d.ts","sourceRoot":"","sources":["../../src/tunnel/CloudflareQuickProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACb,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,8BAA8B;IAC7C,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAc3E;AAED,qBAAa,uBAAwB,YAAW,cAAc;IAC5D,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAsB;IACjD,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAK;IAEhC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,IAAI,EAAE,8BAA8B;IAM1C,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAQrC,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;YASzC,UAAU;IAwGxB,OAAO,CAAC,aAAa;YAIP,UAAU;CAmCzB"}
@@ -26,6 +26,29 @@
26
26
  import fs from 'node:fs';
27
27
  import path from 'node:path';
28
28
  import { bin, install, Tunnel } from 'cloudflared';
29
+ /**
30
+ * Classify a cloudflared quick-tunnel failure into a fixed-substring reason the
31
+ * TunnelManager parses to a ProviderFailureReason. Pure + exported so the
32
+ * decision boundary (esp. the Cloudflare 429/1015 rate-limit detection) is
33
+ * unit-testable. Depends on the caller having captured cloudflared's real stderr
34
+ * (the 'stderr' event) into `stderr` — otherwise the rate-limit text never
35
+ * reaches the haystack and the classification falls through to generic.
36
+ */
37
+ export function classifyQuickTunnelError(msg, stderr) {
38
+ const haystack = `${msg} ${stderr}`.toLowerCase();
39
+ // Cloudflare's quick-tunnel rate-limit surfaces as 429 / error 1015.
40
+ if (haystack.includes('429') || haystack.includes('1015') || haystack.includes('rate limit') || haystack.includes('too many requests')) {
41
+ return new Error(`rate-limited: cloudflared quick-tunnel rate-limited (${msg})`);
42
+ }
43
+ if (haystack.includes('enoent') || haystack.includes('not found') || haystack.includes('binary-missing')) {
44
+ return new Error(`binary-missing: ${msg}`);
45
+ }
46
+ if (haystack.includes('dns') || haystack.includes('eai_again') || haystack.includes('econnrefused') || haystack.includes('network')) {
47
+ return new Error(`network: ${msg}`);
48
+ }
49
+ // Preserve as-is so the manager's classifier sees the original prefix.
50
+ return new Error(msg);
51
+ }
29
52
  export class CloudflareQuickProvider {
30
53
  name = 'cloudflare-quick';
31
54
  tier = 1;
@@ -100,6 +123,16 @@ export class CloudflareQuickProvider {
100
123
  reject(new Error('timeout: cloudflared quick tunnel did not emit a URL within the start budget'));
101
124
  }
102
125
  }, this.startTimeoutMs);
126
+ // Capture cloudflared's REAL stderr. The `cloudflared` wrapper emits a
127
+ // 'stderr' event per child stderr line (incl. the "429 Too Many Requests /
128
+ // error code 1015" rate-limit line). Without listening here, stderrTail
129
+ // stayed empty → 'exit' logged "no stderr captured" → classifyError's
130
+ // 429/1015 detection was DEAD for the exit path, so the manager could never
131
+ // recognize (or back off on) a Cloudflare rate-limit. (Found live 2026-05-31:
132
+ // a quick tunnel 429'd but surfaced only as opaque "process-exit code 1".)
133
+ tunnel.on('stderr', (data) => {
134
+ stderrTail = (stderrTail + ' ' + data).slice(-2000);
135
+ });
103
136
  tunnel.once('url', (url) => {
104
137
  if (resolved)
105
138
  return;
@@ -140,20 +173,7 @@ export class CloudflareQuickProvider {
140
173
  void urlEmitted;
141
174
  }
142
175
  classifyError(msg, stderr) {
143
- const haystack = `${msg} ${stderr}`.toLowerCase();
144
- // Cloudflare's quick-tunnel rate-limit surfaces as 429 / error 1015.
145
- // Both substrings appear in cloudflared's stderr on rate-limit.
146
- if (haystack.includes('429') || haystack.includes('1015') || haystack.includes('rate limit') || haystack.includes('too many requests')) {
147
- return new Error(`rate-limited: cloudflared quick-tunnel rate-limited (${msg})`);
148
- }
149
- if (haystack.includes('enoent') || haystack.includes('not found') || haystack.includes('binary-missing')) {
150
- return new Error(`binary-missing: ${msg}`);
151
- }
152
- if (haystack.includes('dns') || haystack.includes('eai_again') || haystack.includes('econnrefused') || haystack.includes('network')) {
153
- return new Error(`network: ${msg}`);
154
- }
155
- // Preserve as-is so the manager's classifier sees the original prefix.
156
- return new Error(msg);
176
+ return classifyQuickTunnelError(msg, stderr);
157
177
  }
158
178
  async stopHandle(tunnel) {
159
179
  // Force-stop with PID escalation, mirroring TunnelManager.forceStop's
@@ -1 +1 @@
1
- {"version":3,"file":"CloudflareQuickProvider.js","sourceRoot":"","sources":["../../src/tunnel/CloudflareQuickProvider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CloudflareQuickProvider.js","sourceRoot":"","sources":["../../src/tunnel/CloudflareQuickProvider.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.252",
3
+ "version": "1.3.254",
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",
@@ -83,6 +83,22 @@ export function parseBumpType(content) {
83
83
  return match ? match[1].toLowerCase() : null;
84
84
  }
85
85
 
86
+ /**
87
+ * Does this fragment opt into the internal-only ship lane via an
88
+ * `<!-- internal-only -->` marker? Such a fragment has no user-facing surface,
89
+ * so it may omit the "What to Tell Your User" / "Summary of New Capabilities"
90
+ * sections — the assembler auto-fills them ONLY when EVERY contributing fragment
91
+ * is internal-only (see assembleNextMd). The pre-push gate independently verifies
92
+ * the marker against the staged diff (an internal-only fragment whose PR touches
93
+ * runtime `src/` is rejected), so the marker cannot be misused.
94
+ */
95
+ export function hasInternalOnlyMarker(content) {
96
+ return /<!--\s*internal-only\s*-->/i.test(String(content));
97
+ }
98
+
99
+ /** Canonical text auto-filled into the user-facing sections of an all-internal release. */
100
+ export const INTERNAL_ONLY_FILL = 'None — internal change (no user-facing surface).';
101
+
86
102
  /**
87
103
  * Split a fragment body into H2 sections: [{ title, body }] in source order.
88
104
  * `body` excludes the heading line itself and is right-trimmed.
@@ -140,9 +156,15 @@ export function assembleNextMd(fragments) {
140
156
  const extraOrder = [];
141
157
  let maxBumpRank = 0;
142
158
  let sawAnyBump = false;
159
+ // The internal-only lane auto-fills the user-facing sections ONLY when every
160
+ // contributing fragment opts in. Starts true for a non-empty input set and is
161
+ // cleared by the first fragment WITHOUT an <!-- internal-only --> marker, so a
162
+ // single user-facing fragment keeps the full user-section requirement.
163
+ let allInternal = effective.length > 0;
143
164
 
144
165
  for (const frag of effective) {
145
166
  const content = frag.content ?? '';
167
+ if (!hasInternalOnlyMarker(content)) allInternal = false;
146
168
  const bump = parseBumpType(content);
147
169
  if (bump) {
148
170
  sawAnyBump = true;
@@ -183,6 +205,20 @@ export function assembleNextMd(fragments) {
183
205
  );
184
206
  }
185
207
 
208
+ // Internal-only lane: when EVERY contributing fragment is marked
209
+ // <!-- internal-only -->, the change has no user-facing surface, so the two
210
+ // user-facing sections are auto-filled rather than hand-written. This keeps the
211
+ // shared validator (pre-push AND publish) satisfied without forcing
212
+ // "None — internal" boilerplate by hand. We auto-fill ONLY missing sections, so
213
+ // an internal fragment that DOES say something user-facing is preserved as-is;
214
+ // and because `allInternal` is false whenever any fragment lacks the marker, a
215
+ // genuinely user-facing change that omits these sections still fails validation.
216
+ if (allInternal) {
217
+ for (const title of ['What to Tell Your User', 'Summary of New Capabilities']) {
218
+ if (!merged.has(title)) merged.set(title, [INTERNAL_ONLY_FILL]);
219
+ }
220
+ }
221
+
186
222
  // Emit canonical sections first (only when present), then extras in
187
223
  // first-seen order.
188
224
  const titleOrder = [
@@ -14,7 +14,7 @@ import fs from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  import { validateGuideContent } from './upgrade-guide-validator.mjs';
17
- import { assembleNextMd, gatherFragmentInputs } from './assemble-next-md.mjs';
17
+ import { assembleNextMd, gatherFragmentInputs, hasInternalOnlyMarker } from './assemble-next-md.mjs';
18
18
 
19
19
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
20
  const ROOT = path.resolve(__dirname, '..');
@@ -186,6 +186,29 @@ try {
186
186
  );
187
187
  }
188
188
 
189
+ // ── 3c. Internal-only lane verification (objective gate) ──────────────
190
+ // A release fragment marked <!-- internal-only --> may omit the user-facing
191
+ // sections — the assembler auto-fills "None — internal" for an all-internal
192
+ // release. That is ONLY valid for changes with no shipped runtime surface.
193
+ // Verify against the diff: if any staged internal-only fragment accompanies a
194
+ // runtime src/ change, REJECT — a user-facing change must not skip
195
+ // "What to Tell Your User" / "Summary of New Capabilities". This is the
196
+ // objective gate that keeps the marker from being misused (the agent sets it,
197
+ // the diff verifies it). tests/docs/scripts-only changes are fine.
198
+ const internalOnlyFragments = fragmentChanges.filter(f => {
199
+ try { return hasInternalOnlyMarker(fs.readFileSync(path.join(ROOT, f), 'utf-8')); }
200
+ catch { return false; }
201
+ });
202
+ if (internalOnlyFragments.length > 0 && srcChanges.length > 0) {
203
+ errors.push(
204
+ `Internal-only release fragment(s) accompany ${srcChanges.length} runtime src/ change(s):\n` +
205
+ internalOnlyFragments.slice(0, 5).map(f => ` • ${f} (marked <!-- internal-only -->)`).join('\n') + '\n' +
206
+ ` The internal-only lane (which auto-fills the user-facing release sections) is ONLY for changes ` +
207
+ `with no shipped runtime surface (tests / docs / scripts). Either remove the marker and write the ` +
208
+ `"What to Tell Your User" + "Summary of New Capabilities" sections, or split the src/ change into its own PR.`
209
+ );
210
+ }
211
+
189
212
  // ── 4. Adapter contract test gate ─────────────────────────────────────
190
213
  // If messaging adapter source files changed, require contract test evidence.
191
214
  // This prevents shipping integration code verified only by mocked unit tests.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-04T23:01:14.429Z",
5
- "instarVersion": "1.3.252",
4
+ "generatedAt": "2026-06-04T23:23:29.388Z",
5
+ "instarVersion": "1.3.254",
6
6
  "entryCount": 198,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,39 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Added an internal-only release-note lane to the instar-dev ship gate. A release-note
9
+ fragment that has no user-facing surface (test-only, docs-only, or build/CI scripts
10
+ with no `src/` runtime change) can now carry an `<!-- internal-only -->` marker and
11
+ OMIT the two user-facing sections ("What to Tell Your User", "Summary of New
12
+ Capabilities"). The shared assembler (`scripts/assemble-next-md.mjs`) auto-fills those
13
+ sections with "None — internal change (no user-facing surface)." — but ONLY when every
14
+ fragment in the release is internal-only, so a real user-facing change can never skip
15
+ telling the user.
16
+
17
+ The marker is verified objectively: `scripts/pre-push-gate.js` rejects an
18
+ `<!-- internal-only -->` fragment whose diff touches a runtime `src/` file. The
19
+ side-effects review and decision-trace are unchanged — only the user-facing release
20
+ boilerplate is lifted, and only for changes that provably have no user-facing surface.
21
+
22
+ This very fragment uses the new lane (this PR is scripts + tests only), dogfooding it.
23
+
24
+ ## What to Tell Your User
25
+
26
+ None — internal change (no user-facing surface).
27
+
28
+ ## Summary of New Capabilities
29
+
30
+ None — internal change (no user-facing surface).
31
+
32
+ ## Evidence
33
+
34
+ - 45 tests green across `tests/unit/assemble-next-md.test.ts` (auto-fill on all-internal,
35
+ validator passes the auto-filled guide, real content preserved when any fragment is
36
+ user-facing, marker detection, self-... ) and `tests/unit/pre-push-gate.test.ts` (the
37
+ objective §3c marker-vs-diff gate). `node --check scripts/pre-push-gate.js` clean.
38
+ - The auto-fill lives in the assembler shared by BOTH the pre-push gate and the publish
39
+ gate (`check-upgrade-guide.js`), so the two can never disagree about a release note.
@@ -0,0 +1,42 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ **Quick tunnels now correctly recognize when Cloudflare is rate-limiting them.**
9
+ When the zero-config quick tunnel failed because Cloudflare's free tunnel service
10
+ returned "429 Too Many Requests", instar only saw an opaque "process exited" with
11
+ no detail — it couldn't tell a rate-limit apart from any other failure, so it
12
+ couldn't react appropriately. The provider now captures cloudflared's real error
13
+ output, so a rate-limit is identified as exactly that and handed to the tunnel
14
+ manager's failure handling with the right reason.
15
+
16
+ ## What to Tell Your User
17
+
18
+ Nothing to configure. If your quick tunnel ever gets rate-limited by Cloudflare,
19
+ instar now recognizes it as a rate-limit (instead of a generic failure), which
20
+ lets it respond more sensibly under that degraded condition. Named tunnels and
21
+ the healthy path are unaffected.
22
+
23
+ ## Summary of New Capabilities
24
+
25
+ - `classifyQuickTunnelError(msg, stderr)` — pure, exported, unit-tested
26
+ classifier mapping a cloudflared failure to a fixed reason
27
+ (rate-limited / binary-missing / network / passthrough).
28
+ - `CloudflareQuickProvider` now listens to the cloudflared wrapper's `stderr`
29
+ event, so the rate-limit text actually reaches the classifier (it never did
30
+ before, which made the 429 detection dead on the process-exit path).
31
+
32
+ ## Evidence
33
+
34
+ - Found live (2026-05-31): a quick tunnel hit Cloudflare 429, but instar
35
+ surfaced only "process-exit code 1: no stderr captured"; the 429 was visible
36
+ only by running cloudflared directly.
37
+ - Tests: `tests/unit/CloudflareQuickProvider.test.ts` (6) — the classifier
38
+ across all reason branches, plus a mock-cloudflared test proving the new
39
+ stderr listener feeds a 429 line into the classifier so start() rejects with
40
+ rate-limited. `tsc --noEmit` clean.
41
+ - Spec: tunnel-failure-resilience (the manager's single-owner failure handling
42
+ this classification feeds).
@@ -0,0 +1,77 @@
1
+ # Side-Effects Review — internal-only release-note lane (mechanism)
2
+
3
+ **Version / slug:** `internal-only-ship-lane`
4
+ **Date:** `2026-06-04`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `required (release-critical CI machinery)`
7
+
8
+ ## Summary of the change
9
+
10
+ Every release-note fragment currently must carry all four sections — including the
11
+ two user-facing ones (**What to Tell Your User**, **Summary of New Capabilities**).
12
+ For a change with NO user-facing surface (test-only, docs-only, build/CI scripts with
13
+ no `src/` runtime change) that copy is pure boilerplate ("None — internal"), which is
14
+ disproportionate ceremony (the codex-#25 friction; the #758 telegraph test-flake fix
15
+ had to hand-write two "None — internal" sections).
16
+
17
+ This adds an **internal-only lane** (operator-approved). A fragment opts in with an
18
+ `<!-- internal-only -->` marker and may OMIT the two user-facing sections. The
19
+ assembler auto-fills them with a canonical "None — internal change…" ONLY when
20
+ *every* contributing fragment is internal-only. The objective gate: the pre-push gate
21
+ verifies the marker against the diff — an internal-only fragment whose change touches
22
+ runtime `src/` is REJECTED. Side-effects review + decision-trace are unchanged — safety
23
+ and auditability never get a lighter lane.
24
+
25
+ This PR is the **mechanism** (assembler + pre-push verification + tests). The
26
+ deployed-skill documentation (`skills/instar-dev/SKILL.md` + a PostUpdateMigrator
27
+ migration to redeploy it) is a tracked follow-up.
28
+
29
+ ## Decision-point inventory
30
+
31
+ 1. **assembler auto-fill** (`assembleNextMd`): when `allInternal` (every fragment
32
+ marked) and a user-facing canonical section is missing from the merged map, inject
33
+ `INTERNAL_ONLY_FILL`. Never auto-fills when any fragment is non-internal.
34
+ 2. **pre-push verification** (`pre-push-gate.js` §3c): an internal-only fragment +
35
+ any `src/*.ts` change in the diff → error.
36
+
37
+ ## 1. Over-block (what still fails / is required)
38
+
39
+ - A genuinely user-facing change that OMITS the user sections still fails: it is not
40
+ `allInternal` (it has no marker, or the gate rejects the marker on a `src/` diff), so
41
+ the assembler does NOT auto-fill, and `validateGuideContent` reports the missing
42
+ sections. (test: "does NOT auto-fill when any fragment is user-facing".)
43
+ - `What Changed` + `Evidence` are still required; the Evidence-bar (fix-claim →
44
+ Evidence) is untouched.
45
+ - All existing fragment validations (inline-code / camelCase / fenced-block leakage,
46
+ malformed fragment, bump tier) are unchanged — 25 pre-existing assembler tests +
47
+ the full pre-push suite stay green.
48
+
49
+ ## 2. Under-block (what it now allows)
50
+
51
+ - An all-internal release may omit the two user-facing sections; the assembler fills
52
+ them. This is gated objectively: the marker is verified against the diff at pre-push,
53
+ so it cannot be set on a runtime `src/` change to hide user-facing impact. The agent
54
+ sets the marker; the diff verifies it. (test: source-presence of the §3c gate +
55
+ `hasInternalOnlyMarker`.)
56
+ - Worst case of a wrong marker on a non-src change (e.g. a scripts change that DOES have
57
+ user impact): the publish notes say "None — internal" for that release. Bounded and
58
+ low-harm — scripts/docs/test changes have no agent-user runtime surface by
59
+ construction.
60
+
61
+ ## 3. Blast radius
62
+
63
+ - `scripts/assemble-next-md.mjs` — pure function; new `hasInternalOnlyMarker` +
64
+ `INTERNAL_ONLY_FILL` exports + the auto-fill branch.
65
+ - `scripts/pre-push-gate.js` — one new §3c check (reuses existing `srcChanges` /
66
+ `fragmentChanges`).
67
+ - Tests only otherwise. NO `src/` change, no runtime/agent surface. The assembler is
68
+ shared by BOTH the pre-push gate and the publish gate (`check-upgrade-guide.js`), so
69
+ the auto-fill keeps both consistent — the change was deliberately placed in the shared
70
+ assembler so the two gates can never disagree.
71
+
72
+ ## 4. Reversibility
73
+
74
+ Fully reversible: revert the two scripts + test edits. No state, no persisted format
75
+ (the `<!-- internal-only -->` marker is just a fragment comment — a fragment without it
76
+ behaves exactly as before). Verified: 45 tests green (assemble-next-md + pre-push-gate
77
+ suites), `node --check` clean on the gate.
@@ -0,0 +1,63 @@
1
+ # Side effects — quick-tunnel captures cloudflared stderr (429 detection)
2
+
3
+ ## What was broken
4
+
5
+ `CloudflareQuickProvider` (the Tier-1 zero-config quick tunnel) classified
6
+ failures via `classifyError(msg, stderr)`, whose rate-limit branch checks the
7
+ text for `429` / `1015` / "rate limit" / "too many requests". But the provider
8
+ listened only to the cloudflared wrapper's `url` / `error` / `exit` events — it
9
+ **never listened to the wrapper's `stderr` event**, which is the only place
10
+ cloudflared's real stderr (including the `429 Too Many Requests` / `error code
11
+ 1015` rate-limit line) is delivered. So `stderrTail` stayed empty, `exit` logged
12
+ the opaque `process-exit code 1: no stderr captured`, and the 429 branch was
13
+ **dead code on the exit path** — the manager could never recognize (or back off
14
+ on / surface) a Cloudflare rate-limit.
15
+
16
+ Found live (2026-05-31): a quick-tunnel start hit Cloudflare 429, but instar
17
+ surfaced only "process-exit code 1: no stderr captured"; the 429 was visible
18
+ only by running `cloudflared tunnel --url` directly.
19
+
20
+ ## The fix
21
+
22
+ 1. `CloudflareQuickProvider.startInner` now registers
23
+ `tunnel.on('stderr', (data) => { stderrTail = (stderrTail + ' ' + data).slice(-2000); })`,
24
+ capturing cloudflared's real stderr (bounded to the last 2000 chars). The
25
+ existing `error`/`exit` handlers already feed `stderrTail` into the
26
+ classifier, so the 429/1015 text now reaches it.
27
+ 2. The classification logic is extracted to a pure, exported
28
+ `classifyQuickTunnelError(msg, stderr)` (unchanged behavior), so the
29
+ decision boundary is unit-testable.
30
+
31
+ ## Who is affected
32
+
33
+ - **Any agent with a quick tunnel that hits Cloudflare 429** (the free
34
+ trycloudflare service rate-limits under load): the failure is now classified
35
+ `rate-limited:` instead of opaque `process-exit`, so the TunnelManager's
36
+ failure handling (per the tunnel-failure-resilience spec) acts on the correct
37
+ reason. No behavior change on the success path or for non-rate-limited
38
+ failures (those classify exactly as before).
39
+
40
+ ## Blast radius
41
+
42
+ - 1 source file: `src/tunnel/CloudflareQuickProvider.ts` (one added event
43
+ listener + a pure-function extraction). No config, no schema, no migration —
44
+ picked up by existing agents on the normal dist update. The TunnelManager,
45
+ other providers, and the success path are untouched.
46
+
47
+ ## Failure modes considered
48
+
49
+ - **Listener fires after resolution?** Harmless — it only appends to a bounded
50
+ buffer; once `url` resolves, the buffer is unused.
51
+ - **stderr never arrives (true silent exit)?** Behavior is exactly as before:
52
+ `stderrTail` empty → `exit` logs "no stderr captured" → classified generic.
53
+ The fix can only ADD signal, never remove it.
54
+ - **Buffer growth?** Bounded to the last 2000 chars via `.slice(-2000)`.
55
+
56
+ ## Tests
57
+
58
+ `tests/unit/CloudflareQuickProvider.test.ts` (6): `classifyQuickTunnelError`
59
+ across 429/1015/"too many requests"/"rate limit", ENOENT→binary-missing,
60
+ DNS/ECONNREFUSED→network, and unrecognized→preserved; plus a mock-`cloudflared`
61
+ test proving the new `stderr` listener feeds a 429 stderr line into the
62
+ classifier so `start()` rejects `rate-limited` (rather than the opaque
63
+ `process-exit`). `tsc --noEmit` clean.