@synkro-sh/cli 1.6.71 → 1.6.73

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/bootstrap.js CHANGED
@@ -145,7 +145,7 @@ function installCCHooks(settingsPath, config) {
145
145
  {
146
146
  type: "command",
147
147
  command: config.bashJudgeScriptPath,
148
- timeout: 30
148
+ timeout: 50
149
149
  }
150
150
  ],
151
151
  [SYNKRO_MARKER]: true
@@ -156,12 +156,12 @@ function installCCHooks(settingsPath, config) {
156
156
  {
157
157
  type: "command",
158
158
  command: config.editPrecheckScriptPath,
159
- timeout: 30
159
+ timeout: 50
160
160
  },
161
161
  {
162
162
  type: "command",
163
163
  command: config.cwePrecheckScriptPath,
164
- timeout: 30
164
+ timeout: 50
165
165
  },
166
166
  {
167
167
  type: "command",
@@ -177,7 +177,7 @@ function installCCHooks(settingsPath, config) {
177
177
  {
178
178
  type: "command",
179
179
  command: config.agentJudgeScriptPath,
180
- timeout: 30
180
+ timeout: 50
181
181
  }
182
182
  ],
183
183
  [SYNKRO_MARKER]: true
@@ -188,7 +188,7 @@ function installCCHooks(settingsPath, config) {
188
188
  {
189
189
  type: "command",
190
190
  command: config.planJudgeScriptPath,
191
- timeout: 45
191
+ timeout: 50
192
192
  }
193
193
  ],
194
194
  [SYNKRO_MARKER]: true
@@ -200,7 +200,7 @@ function installCCHooks(settingsPath, config) {
200
200
  {
201
201
  type: "command",
202
202
  command: config.mcpGateScriptPath,
203
- timeout: 30
203
+ timeout: 50
204
204
  }
205
205
  ],
206
206
  [SYNKRO_MARKER]: true
@@ -1689,7 +1689,12 @@ async function cloudGrade(surface: string, prompt: string, jwt: string, timeoutM
1689
1689
  // JWT (the Worker derives the org from the token and routes to that org's
1690
1690
  // container). Any non-2xx throws so the caller's catch fails open.
1691
1691
  const CONTAINERS_URL = process.env.SYNKRO_CONTAINERS_URL || 'https://containers.synkro.sh';
1692
- async function hostedGrade(role: GradeRole, prompt: string, jwt: string, timeoutMs = 24000, agentKind: AgentKind = 'claude_code'): Promise<string> {
1692
+ // Cloud grades cross the network and can hit a cold/queued worker, so they get a
1693
+ // longer leash than on-device channel grades. The hook chain is sized around it:
1694
+ // grade abort (45s) < watchdog (48s) < CC hook budget (50s) \u2014 the grade aborts
1695
+ // FIRST so the caller's catch fails open cleanly before CC ever force-kills us.
1696
+ const CLOUD_GRADE_TIMEOUT_MS = 45000;
1697
+ async function hostedGrade(role: GradeRole, prompt: string, jwt: string, timeoutMs = CLOUD_GRADE_TIMEOUT_MS, agentKind: AgentKind = 'claude_code'): Promise<string> {
1693
1698
  const resp = await fetch(CONTAINERS_URL + '/grade/submit', {
1694
1699
  method: 'POST',
1695
1700
  headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
@@ -1705,18 +1710,20 @@ async function hostedGrade(role: GradeRole, prompt: string, jwt: string, timeout
1705
1710
  return String(data.result || '');
1706
1711
  }
1707
1712
 
1708
- // Default 24s \u2014 MUST stay below the CC hook timeout (30s for edit/bash/cwe in
1709
- // ccHookConfig.ts) so the AbortSignal fires and the caller's catch fails open
1710
- // cleanly. If this matches or exceeds the CC budget, CC force-kills the bun
1711
- // process mid-fetch (exit 142 / SIGALRM) and surfaces the in-flight grader
1712
- // prompt as a hook error instead of a clean pass.
1713
+ // Local-channel default 24s \u2014 MUST stay below the CC hook timeout (50s for
1714
+ // edit/bash/cwe in ccHookConfig.ts) so the AbortSignal fires and the caller's
1715
+ // catch fails open cleanly. A hung on-device worker should fail open fast; only
1716
+ // cloud grades (network + cold workers) get the longer 45s leash below.
1713
1717
  export async function localGrade(surface: string, prompt: string, timeoutMs = 24000, agentKind: AgentKind = 'claude_code'): Promise<string> {
1714
1718
  const jwt = loadJwt();
1715
1719
  if (!jwt) throw new Error('NO_JWT');
1716
1720
  // Cloud-container: the grader runs on Cloudflare Containers \u2014 route to the
1717
1721
  // hosted ingress instead of the local channel. Same prompt + parseVerdict.
1722
+ // Cloud grades get the full 45s (not the local 24s): the round-trip + a cold
1723
+ // worker routinely needs >24s, which is exactly what was timing out and
1724
+ // failing open on big bash prompts.
1718
1725
  if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') {
1719
- return hostedGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, timeoutMs, agentKind);
1726
+ return hostedGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, CLOUD_GRADE_TIMEOUT_MS, agentKind);
1720
1727
  }
1721
1728
  // BYOK grading mode routes the grade through an LLM API instead of the
1722
1729
  // on-device channel worker pool. The grader prompt + parseVerdict are shared.
@@ -1727,14 +1734,14 @@ export async function localGrade(surface: string, prompt: string, timeoutMs = 24
1727
1734
  return channelGrade(ROLE_MAP[surface] || 'grade-edit', prompt, jwt, 18929, timeoutMs, agentKind);
1728
1735
  }
1729
1736
 
1730
- // Default 24s \u2014 was 45000, which EXCEEDED the CC cwe hook timeout (30s) and
1731
- // guaranteed a force-kill on any slow grade. Two cwe chunks grade in parallel
1732
- // within the same CC budget, so each must finish well under it.
1737
+ // Local-channel default 24s. Two cwe chunks grade in parallel within the same CC
1738
+ // budget (now 50s), so each must finish under it. Cloud cwe grades take the 45s
1739
+ // hosted leash (parallel chunks \u2192 wall-clock ~45s, still inside the 50s budget).
1733
1740
  export async function localGradeCwe(prompt: string, agentKind: AgentKind = 'claude_code', timeoutMs = 24000): Promise<string> {
1734
1741
  const jwt = loadJwt();
1735
1742
  if (!jwt) throw new Error('NO_JWT');
1736
1743
  if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') {
1737
- return hostedGrade('grade-cwe', prompt, jwt, timeoutMs, agentKind);
1744
+ return hostedGrade('grade-cwe', prompt, jwt, CLOUD_GRADE_TIMEOUT_MS, agentKind);
1738
1745
  }
1739
1746
  return channelGrade('grade-cwe', prompt, jwt, 18930, timeoutMs, agentKind);
1740
1747
  }
@@ -3540,6 +3547,16 @@ export function graderUnavailableMessage(hook: string, target: string, errorMess
3540
3547
  const agent = agentKind === 'cursor' ? 'Cursor' : 'Claude Code';
3541
3548
  return hook + ' ' + target + ' \u2192 local grader not configured for ' + agent + '. Add this agent to your synkro.toml file and run \`synkro install\`.';
3542
3549
  }
3550
+ // In cloud-container mode the grade ran against the hosted ingress, NOT a local
3551
+ // worker \u2014 say so, and distinguish a timeout (slow/cold grade) from a true
3552
+ // outage so "local grader unavailable" stops masquerading as a dead grader.
3553
+ if (process.env.SYNKRO_DEPLOY_LOCATION === 'cloud') {
3554
+ const isTimeout = /tim(e|ed)\\s*out|aborted|the operation was aborted/i.test(errorMessage);
3555
+ if (isTimeout) {
3556
+ return hook + ' ' + target + ' \u2192 cloud grade timed out (45s), skipped';
3557
+ }
3558
+ return hook + ' ' + target + ' \u2192 cloud grader unavailable, skipped';
3559
+ }
3543
3560
  return hook + ' ' + target + ' \u2192 local grader unavailable, skipped';
3544
3561
  }
3545
3562
 
@@ -3635,7 +3652,7 @@ const agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.i
3635
3652
 
3636
3653
  async function main() {
3637
3654
  setupCursorHookSignals();
3638
- installHookWatchdog(27000);
3655
+ installHookWatchdog(48000);
3639
3656
  try {
3640
3657
  const input = await readStdin();
3641
3658
  if (!input.trim()) { outputEmpty(); return; }
@@ -4029,7 +4046,7 @@ function scanPackageCapabilities(pkgName: string, cwd: string): PackageCapabilit
4029
4046
 
4030
4047
  async function main() {
4031
4048
  setupCursorHookSignals();
4032
- installHookWatchdog(27000);
4049
+ installHookWatchdog(48000);
4033
4050
  try {
4034
4051
  const input = await readStdin();
4035
4052
  if (!input.trim()) { outputEmpty(); return; }
@@ -4942,7 +4959,7 @@ function isDuplicate(command: string, sessionId: string): boolean {
4942
4959
 
4943
4960
  async function main() {
4944
4961
  setupCursorHookSignals();
4945
- installHookWatchdog(27000);
4962
+ installHookWatchdog(48000);
4946
4963
  try {
4947
4964
  const input = await readStdin();
4948
4965
  if (!input.trim()) { outputEmpty(); return; }
@@ -5214,7 +5231,7 @@ const agentKind = (process.env.SYNKRO_HOOK_FORMAT === 'cursor' || process.argv.i
5214
5231
 
5215
5232
  async function main() {
5216
5233
  setupCursorHookSignals();
5217
- installHookWatchdog(27000);
5234
+ installHookWatchdog(48000);
5218
5235
  try {
5219
5236
  const input = await readStdin();
5220
5237
  if (!input.trim()) { outputEmpty(); return; }
@@ -5451,7 +5468,7 @@ function appendReviewToPlan(planFile: string, verdict: string): void {
5451
5468
 
5452
5469
  async function main() {
5453
5470
  setupCursorHookSignals();
5454
- installHookWatchdog(42000);
5471
+ installHookWatchdog(48000);
5455
5472
  try {
5456
5473
  const input = await readStdin();
5457
5474
  if (!input.trim()) { outputEmpty(); return; }
@@ -10386,7 +10403,7 @@ function writeConfigEnv(opts) {
10386
10403
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
10387
10404
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
10388
10405
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
10389
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.71")}`
10406
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.73")}`
10390
10407
  ];
10391
10408
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
10392
10409
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -10560,6 +10577,16 @@ async function verifyCloudGrader(jwt2) {
10560
10577
  return false;
10561
10578
  }
10562
10579
  console.log(` \u2713 ${healthy} Claude worker(s) healthy`);
10580
+ console.log(" Running smoke grade (a worker scores a known-risky edit; up to 120s)...");
10581
+ const started = Date.now();
10582
+ const ticker = setInterval(() => {
10583
+ const secs = Math.round((Date.now() - started) / 1e3);
10584
+ process.stdout.write(`\r \u2026grading (${secs}s elapsed) `);
10585
+ }, 1e3);
10586
+ const stopTicker = () => {
10587
+ clearInterval(ticker);
10588
+ process.stdout.write("\r\x1B[2K");
10589
+ };
10563
10590
  try {
10564
10591
  const probe = 'Grade this edit. File: t.py. Change: os.system(request.args["cmd"])';
10565
10592
  const r = await fetch(`${base}/grade/submit`, {
@@ -10568,6 +10595,7 @@ async function verifyCloudGrader(jwt2) {
10568
10595
  body: JSON.stringify({ role: "grade-edit", payload: probe, content: probe, agent_kind: "claude_code" }),
10569
10596
  signal: AbortSignal.timeout(12e4)
10570
10597
  });
10598
+ stopTicker();
10571
10599
  const raw = await r.text();
10572
10600
  let body = {};
10573
10601
  try {
@@ -10591,6 +10619,7 @@ async function verifyCloudGrader(jwt2) {
10591
10619
  await printWorkerDebug(base, jwt2);
10592
10620
  return false;
10593
10621
  } catch (e) {
10622
+ stopTicker();
10594
10623
  const reason = e?.name === "TimeoutError" ? "the grade never returned within 120s (worker hung \u2014 usually claude auth)" : `the request failed (${e?.message || "network"})`;
10595
10624
  console.warn(` \u2717 smoke grade FAILED: ${reason}.`);
10596
10625
  console.warn(" The container is provisioned but a grade does not complete; re-run `synkro install` \u2192 cloud.\n");
@@ -12063,7 +12092,14 @@ function writePluginFiles() {
12063
12092
  c.pluginSettingsPath,
12064
12093
  JSON.stringify({
12065
12094
  fastMode: true,
12066
- enabledMcpjsonServers: ["synkro-local"]
12095
+ enabledMcpjsonServers: ["synkro-local"],
12096
+ // Extended thinking is pure latency tax on a structured verdict grade and
12097
+ // emits reasoning the verdict parser isn't tuned for. effortLevel only
12098
+ // SOFTENS it; CLAUDE_CODE_DISABLE_THINKING=1 is the authoritative off
12099
+ // switch. Mirror the cloud pool (pool-config.sh) so local + cloud graders
12100
+ // behave identically.
12101
+ effortLevel: "low",
12102
+ env: { CLAUDE_CODE_DISABLE_THINKING: "1" }
12067
12103
  }, null, 2) + "\n",
12068
12104
  "utf-8"
12069
12105
  );
@@ -12304,7 +12340,7 @@ tmux kill-session -t "=$SESSION" 2>/dev/null || true
12304
12340
  # Start claude inside a detached tmux session so it has a real pty.
12305
12341
  # Redirect stderr to the log so we can see why it dies.
12306
12342
  tmux new-session -d -s "$SESSION" \\
12307
- "SYNKRO_CHANNEL_PORT=${CHANNEL_1_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
12343
+ "SYNKRO_CHANNEL_PORT=${CHANNEL_1_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
12308
12344
 
12309
12345
  # Claude's --dangerously-load-development-channels shows a confirmation
12310
12346
  # prompt: option 1 = "I am using this for local development" (accept),
@@ -12366,7 +12402,7 @@ log "claude version: $(claude --version 2>&1 | head -1)"
12366
12402
  tmux kill-session -t "=$SESSION" 2>/dev/null || true
12367
12403
 
12368
12404
  tmux new-session -d -s "$SESSION" \\
12369
- "SYNKRO_CHANNEL_PORT=${CHANNEL_2_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
12405
+ "SYNKRO_CHANNEL_PORT=${CHANNEL_2_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
12370
12406
 
12371
12407
  sleep 3
12372
12408
  if tmux has-session -t "=$SESSION" 2>/dev/null; then
@@ -12422,7 +12458,7 @@ log "claude version: $(claude --version 2>&1 | head -1)"
12422
12458
  tmux kill-session -t "=$SESSION" 2>/dev/null || true
12423
12459
 
12424
12460
  tmux new-session -d -s "$SESSION" \\
12425
- "SYNKRO_CHANNEL_PORT=${CHANNEL_3_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
12461
+ "SYNKRO_CHANNEL_PORT=${CHANNEL_3_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
12426
12462
 
12427
12463
  sleep 3
12428
12464
  if tmux has-session -t "=$SESSION" 2>/dev/null; then
@@ -12478,7 +12514,7 @@ log "claude version: $(claude --version 2>&1 | head -1)"
12478
12514
  tmux kill-session -t "=$SESSION" 2>/dev/null || true
12479
12515
 
12480
12516
  tmux new-session -d -s "$SESSION" \\
12481
- "SYNKRO_CHANNEL_PORT=${CHANNEL_4_PORT} claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
12517
+ "SYNKRO_CHANNEL_PORT=${CHANNEL_4_PORT} CLAUDE_CODE_DISABLE_THINKING=1 claude --dangerously-load-development-channels server:synkro-local --dangerously-skip-permissions --setting-sources project,local --model claude-sonnet-4-6 2>>$LOG; echo 'claude exited with code '$'?' >> $LOG"
12482
12518
 
12483
12519
  sleep 3
12484
12520
  if tmux has-session -t "=$SESSION" 2>/dev/null; then
@@ -14019,7 +14055,7 @@ var args = process.argv.slice(2);
14019
14055
  var cmd = args[0] || "";
14020
14056
  var subArgs = args.slice(1);
14021
14057
  function printVersion() {
14022
- console.log("1.6.71");
14058
+ console.log("1.6.73");
14023
14059
  }
14024
14060
  function printHelp2() {
14025
14061
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents