19cc6123e901

extract process-readiness wait with 120s adoption budget and bounded diagnostics

time
2026-09-11 02:29 UTC · 1 month ago
machine
koh2
agent
synthetic/hf:zai-org/GLM-5.2
session
adoption endpoint readiness
parent
7fa035f54246
6 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1scripts/check-cross-client-sync.mjs+5 −14scripts/process-readiness.mjs+19 −0scripts/process-readiness.test.ts+22 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.2.42",4+  "version": "5.2.43",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "5.2.42",5+  "version": "5.2.43",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "5.2.42",3+  "version": "5.2.43",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

scripts/check-cross-client-sync.mjs+5 −14
@@ -5,6 +5,7 @@55 import { createPrivateKey, createPublicKey, sign as signEd25519Node } from "node:crypto";66 import { tmpdir } from "node:os";77 import { generateKeyPairFromSeed } from "@stablelib/x25519";8+import { waitForFile } from "./process-readiness.mjs";89 910 const browserRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");1011 const androidRoot = resolve(browserRoot, "../peach-android");@@ -457,16 +458,6 @@457458   return `code=${result.code ?? "none"} signal=${result.signal ?? "none"}; stdout=${JSON.stringify(output.stdout)} stderr=${JSON.stringify(output.stderr)}`;458459 }459460 460-async function waitForFile(path, child) {461-  const deadline = Date.now() + 30_000;462-  while (!existsSync(path)) {463-    if (child.exitCode !== null) throw new Error(`endpoint exited before publishing ${path} (${child.exitCode})`);464-    if (Date.now() > deadline) throw new Error(`timed out waiting for ${path}`);465-    await new Promise((resolve) => setTimeout(resolve, 50));466-  }467-  return readFileSync(path, "utf8").trim();468-}469-470461 function readTranscript(path) {471462   return JSON.parse(readFileSync(path, "utf8"));472463 }@@ -525,11 +516,11 @@525516   try {526517     if (initiator === "android") {527518       first = launchAndroid("initiator");528-      const port = await waitForFile(androidPortFile, first);519+      const port = await waitForFile(androidPortFile, first, 30_000);529520       second = launchBrowser("responder", port, browserTranscript);530521     } else {531522       first = launchBrowser("initiator", undefined, browserTranscript, browserPortFile);532-      const port = await waitForFile(browserPortFile, first);523+      const port = await waitForFile(browserPortFile, first, 30_000);533524       second = launchAndroid("responder", port);534525     }535526     const timeout = new Promise((_, reject) => {@@ -593,10 +584,10 @@593584   try {594585     if (source === "android") {595586       first = launchAndroid("source");596-      second = launchBrowser("receiver", await waitForFile(portFile, first));587+      second = launchBrowser("receiver", await waitForFile(portFile, first, 120_000));597588     } else {598589       first = launchBrowser("source");599-      second = launchAndroid("receiver", await waitForFile(portFile, first));590+      second = launchAndroid("receiver", await waitForFile(portFile, first, 120_000));600591     }601592     const results = await Promise.all([waitForExit(first), waitForExit(second)]);602593     if (results.some((result) => result.code !== 0)) {

view file

scripts/process-readiness.mjs+19 −0
@@ -0,0 +1,19 @@1+import { existsSync, readFileSync } from "node:fs";2+3+export async function waitForFile(path, child, timeoutMs, pollMs = 50) {4+  const deadline = Date.now() + timeoutMs;5+  while (!existsSync(path)) {6+    if (child.exitCode !== null) {7+      throw new Error(`endpoint exited before publishing ${path} (${child.exitCode}); ${diagnostics(child)}`);8+    }9+    if (Date.now() > deadline) {10+      throw new Error(`timed out after ${timeoutMs}ms waiting for ${path}; ${diagnostics(child)}`);11+    }12+    await new Promise((resolve) => setTimeout(resolve, pollMs));13+  }14+  return readFileSync(path, "utf8").trim();15+}16+17+function diagnostics(child) {18+  return `stdout=${JSON.stringify(child.diagnostics?.stdout ?? "")} stderr=${JSON.stringify(child.diagnostics?.stderr ?? "")}`;19+}

view file

scripts/process-readiness.test.ts+22 −0
@@ -0,0 +1,22 @@1+import { mkdtempSync, writeFileSync } from "node:fs";2+import { tmpdir } from "node:os";3+import { resolve } from "node:path";4+import { describe, expect, it } from "vitest";5+import { waitForFile } from "./process-readiness.mjs";6+7+describe("process endpoint readiness", () => {8+  it("allows a cold endpoint to start after the legacy 30-second boundary", async () => {9+    const path = resolve(mkdtempSync(resolve(tmpdir(), "peach-ready-test-")), "port");10+    const child = { exitCode: null, diagnostics: { stdout: "gradle startup", stderr: "" } };11+    setTimeout(() => writeFileSync(path, "49142"), 35);12+13+    await expect(waitForFile(path, child, 90, 1)).resolves.toBe("49142");14+  });15+16+  it("includes endpoint output when readiness times out", async () => {17+    const path = resolve(mkdtempSync(resolve(tmpdir(), "peach-ready-test-")), "port");18+    const child = { exitCode: null, diagnostics: { stdout: "gradle startup", stderr: "daemon busy" } };19+20+    await expect(waitForFile(path, child, 5, 1)).rejects.toThrow(/gradle startup.*daemon busy/);21+  });22+});

view file