fix: improve gmail tailscale errors

This commit is contained in:
Peter Steinberger
2026-01-07 03:10:35 +01:00
parent ba317588c0
commit 2986447935
2 changed files with 70 additions and 16 deletions

View File

@@ -30,6 +30,7 @@
- Build: import tool-display JSON as a module instead of runtime file reads. Thanks @mukhtharcm for PR #312.
- Browser: fix `browser snapshot`/`browser act` timeouts under Bun by patching Playwrights CDP WebSocket selection. Thanks @azade-c for PR #307.
- Browser: add `--browser-profile` flag and honor profile in tabs routes + browser tool. Thanks @jamesgroat for PR #324.
- Gmail: include tailscale command exit codes/output when hook setup fails (easier debugging).
- Telegram: stop typing after tool results. Thanks @AbhisekBasu1 for PR #322.
- Telegram: include sender identity in group envelope headers. (#336)
- Messages: stop defaulting ack reactions to 👀 when identity emoji is missing.

View File

@@ -2,11 +2,59 @@ import fs from "node:fs";
import path from "node:path";
import { hasBinary } from "../agents/skills.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { runCommandWithTimeout, type SpawnResult } from "../process/exec.js";
import { resolveUserPath } from "../utils.js";
import { normalizeServePath } from "./gmail.js";
let cachedPythonPath: string | null | undefined;
const MAX_OUTPUT_CHARS = 800;
function trimOutput(value: string): string {
const trimmed = value.trim();
if (!trimmed) return "";
if (trimmed.length <= MAX_OUTPUT_CHARS) return trimmed;
return `${trimmed.slice(0, MAX_OUTPUT_CHARS)}`;
}
function formatCommandFailure(command: string, result: SpawnResult): string {
const code = result.code ?? "null";
const signal = result.signal ? `, signal=${result.signal}` : "";
const killed = result.killed ? ", killed=true" : "";
const stderr = trimOutput(result.stderr);
const stdout = trimOutput(result.stdout);
const lines = [`${command} failed (code=${code}${signal}${killed})`];
if (stderr) lines.push(`stderr: ${stderr}`);
if (stdout) lines.push(`stdout: ${stdout}`);
return lines.join("\n");
}
function formatCommandResult(command: string, result: SpawnResult): string {
const code = result.code ?? "null";
const signal = result.signal ? `, signal=${result.signal}` : "";
const killed = result.killed ? ", killed=true" : "";
const stderr = trimOutput(result.stderr);
const stdout = trimOutput(result.stdout);
const lines = [`${command} exited (code=${code}${signal}${killed})`];
if (stderr) lines.push(`stderr: ${stderr}`);
if (stdout) lines.push(`stdout: ${stdout}`);
return lines.join("\n");
}
function formatJsonParseFailure(
command: string,
result: SpawnResult,
err: unknown,
): string {
const reason = err instanceof Error ? err.message : String(err);
return `${command} returned invalid JSON: ${reason}\n${formatCommandResult(
command,
result,
)}`;
}
function formatCommand(command: string, args: string[]): string {
return [command, ...args].join(" ");
}
function findExecutablesOnPath(bins: string[]): string[] {
const pathEnv = process.env.PATH ?? "";
@@ -218,18 +266,20 @@ export async function ensureTailscaleEndpoint(params: {
}): Promise<string> {
if (params.mode === "off") return "";
const status = await runCommandWithTimeout(
["tailscale", "status", "--json"],
{
timeoutMs: 30_000,
},
);
const statusArgs = ["status", "--json"];
const statusCommand = formatCommand("tailscale", statusArgs);
const status = await runCommandWithTimeout(["tailscale", ...statusArgs], {
timeoutMs: 30_000,
});
if (status.code !== 0) {
throw new Error(status.stderr || "tailscale status failed");
throw new Error(formatCommandFailure(statusCommand, status));
}
let parsed: { Self?: { DNSName?: string } };
try {
parsed = JSON.parse(status.stdout) as { Self?: { DNSName?: string } };
} catch (err) {
throw new Error(formatJsonParseFailure(statusCommand, status, err));
}
const parsed = JSON.parse(status.stdout) as {
Self?: { DNSName?: string };
};
const dnsName = parsed.Self?.DNSName?.replace(/\.$/, "");
if (!dnsName) {
throw new Error("tailscale DNS name missing; run tailscale up");
@@ -238,7 +288,6 @@ export async function ensureTailscaleEndpoint(params: {
const target = String(params.port);
const pathArg = normalizeServePath(params.path);
const funnelArgs = [
"tailscale",
params.mode,
"--bg",
"--set-path",
@@ -246,11 +295,15 @@ export async function ensureTailscaleEndpoint(params: {
"--yes",
target,
];
const funnelResult = await runCommandWithTimeout(funnelArgs, {
timeoutMs: 30_000,
});
const funnelCommand = formatCommand("tailscale", funnelArgs);
const funnelResult = await runCommandWithTimeout(
["tailscale", ...funnelArgs],
{
timeoutMs: 30_000,
},
);
if (funnelResult.code !== 0) {
throw new Error(funnelResult.stderr || "tailscale funnel failed");
throw new Error(formatCommandFailure(funnelCommand, funnelResult));
}
const baseUrl = `https://${dnsName}${pathArg}`;