Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sandbox-hint-network-failures.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Stop showing the "possible sandboxed run" hint for ordinary network failures (unreachable host, VPN, DNS) in agent mode. The hint now requires a permission-like error before suggesting a sandbox, and is a single line.
59 changes: 59 additions & 0 deletions packages/cli-core/src/lib/host-execution.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
import { observeHostCapabilityFailure, _resetAgentHostStateProbe } from "./host-execution.ts";
import { setMode } from "../mode.ts";
import { useCaptureLog } from "../test/lib/stubs.ts";

describe("observeHostCapabilityFailure sandbox hinting", () => {
const captured = useCaptureLog();

beforeEach(() => {
setMode("agent");
_resetAgentHostStateProbe();
});

afterEach(() => {
setMode("human");
_resetAgentHostStateProbe();
});

test("a plain connectivity failure (unreachable host, VPN, DNS) does not warn", () => {
observeHostCapabilityFailure("network", new Error("ECONNREFUSED 127.0.0.1:443"));
expect(captured.err).not.toContain("sandboxed run");
});

test("a permission-like network failure warns about a possible sandbox (message)", () => {
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with permission-like code warns about a possible sandbox", () => {
const cause = Object.assign(new Error("operation not permitted"), { code: "EPERM" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with non-permission code does not warn", () => {
const cause = Object.assign(new Error("Connection refused"), { code: "ConnectionRefused" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).not.toContain("sandboxed run");
});

test("browser-launch and localhost-bind failures always warn", () => {
observeHostCapabilityFailure("browser-launch", new Error("spawn ENOENT"));
expect(captured.err).toContain("sandboxed run");
});

test("the hint is a single line", () => {
observeHostCapabilityFailure("network", new Error("EPERM"));
const warnLines = captured.err.split("\n").filter((line) => line.includes("agent mode"));
expect(warnLines).toHaveLength(1);
});

test("does not warn in human mode", () => {
setMode("human");
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).not.toContain("sandboxed run");
});
});
51 changes: 32 additions & 19 deletions packages/cli-core/src/lib/host-execution.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,36 +93,49 @@ function warnAboutSandbox(detail?: string): void {
warnedAboutSandbox = true;

log.warn(
"Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.",
);
log.warn(
"Re-run this command on the host shell before trusting auth, link, env, or API failures.",
"Host-only Clerk state or capabilities may be unavailable in agent mode (possible sandboxed run). If this looks wrong, re-run on the host shell before trusting auth, link, env, or API failures.",
);

if (detail) {
log.debug(detail);
}
}

const PERMISSION_PATTERNS = [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
];

function matchesPermissionPattern(s: string): boolean {
return PERMISSION_PATTERNS.some((pattern) => pattern.test(s));
}

function isPermissionLikeFailure(error: unknown): boolean {
const message = errorMessage(error);
return [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
].some((pattern) => pattern.test(message));
if (!(error instanceof Error)) {
return matchesPermissionPattern(String(error));
}

if (matchesPermissionPattern(error.message)) return true;

const code = (error as NodeJS.ErrnoException).code;
if (code && matchesPermissionPattern(code)) return true;

if (error.cause instanceof Error && isPermissionLikeFailure(error.cause)) return true;

return false;
}

function isLikelySandboxFailure(capability: HostCapability, error: unknown): boolean {
if (
capability === "network" ||
capability === "browser-launch" ||
capability === "localhost-bind"
) {
// browser-launch and localhost-bind only ever fail for host-capability
// reasons, so any failure is a meaningful sandbox signal. Network failures
// are different: a plain unreachable host (VPN, DNS, ECONNREFUSED) is not a
// sandbox, so require a permission-like error before hinting at a sandbox.
if (capability === "browser-launch" || capability === "localhost-bind") {
Comment thread
rafa-thayto marked this conversation as resolved.
return true;
}

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sandbox-hint-network-failures.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Stop showing the "possible sandboxed run" hint for ordinary network failures (unreachable host, VPN, DNS) in agent mode. The hint now requires a permission-like error before suggesting a sandbox, and is a single line.
59 changes: 59 additions & 0 deletions packages/cli-core/src/lib/host-execution.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
import { observeHostCapabilityFailure, _resetAgentHostStateProbe } from "./host-execution.ts";
import { setMode } from "../mode.ts";
import { useCaptureLog } from "../test/lib/stubs.ts";

describe("observeHostCapabilityFailure sandbox hinting", () => {
const captured = useCaptureLog();

beforeEach(() => {
setMode("agent");
_resetAgentHostStateProbe();
});

afterEach(() => {
setMode("human");
_resetAgentHostStateProbe();
});

test("a plain connectivity failure (unreachable host, VPN, DNS) does not warn", () => {
observeHostCapabilityFailure("network", new Error("ECONNREFUSED 127.0.0.1:443"));
expect(captured.err).not.toContain("sandboxed run");
});

test("a permission-like network failure warns about a possible sandbox (message)", () => {
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with permission-like code warns about a possible sandbox", () => {
const cause = Object.assign(new Error("operation not permitted"), { code: "EPERM" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with non-permission code does not warn", () => {
const cause = Object.assign(new Error("Connection refused"), { code: "ConnectionRefused" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).not.toContain("sandboxed run");
});

test("browser-launch and localhost-bind failures always warn", () => {
observeHostCapabilityFailure("browser-launch", new Error("spawn ENOENT"));
expect(captured.err).toContain("sandboxed run");
});

test("the hint is a single line", () => {
observeHostCapabilityFailure("network", new Error("EPERM"));
const warnLines = captured.err.split("\n").filter((line) => line.includes("agent mode"));
expect(warnLines).toHaveLength(1);
});

test("does not warn in human mode", () => {
setMode("human");
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).not.toContain("sandboxed run");
});
});
51 changes: 32 additions & 19 deletions packages/cli-core/src/lib/host-execution.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,36 +93,49 @@ function warnAboutSandbox(detail?: string): void {
warnedAboutSandbox = true;

log.warn(
"Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.",
);
log.warn(
"Re-run this command on the host shell before trusting auth, link, env, or API failures.",
"Host-only Clerk state or capabilities may be unavailable in agent mode (possible sandboxed run). If this looks wrong, re-run on the host shell before trusting auth, link, env, or API failures.",
);

if (detail) {
log.debug(detail);
}
}

const PERMISSION_PATTERNS = [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
];

function matchesPermissionPattern(s: string): boolean {
return PERMISSION_PATTERNS.some((pattern) => pattern.test(s));
}

function isPermissionLikeFailure(error: unknown): boolean {
const message = errorMessage(error);
return [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
].some((pattern) => pattern.test(message));
if (!(error instanceof Error)) {
return matchesPermissionPattern(String(error));
}

if (matchesPermissionPattern(error.message)) return true;

const code = (error as NodeJS.ErrnoException).code;
if (code && matchesPermissionPattern(code)) return true;

if (error.cause instanceof Error && isPermissionLikeFailure(error.cause)) return true;

return false;
}

function isLikelySandboxFailure(capability: HostCapability, error: unknown): boolean {
if (
capability === "network" ||
capability === "browser-launch" ||
capability === "localhost-bind"
) {
// browser-launch and localhost-bind only ever fail for host-capability
// reasons, so any failure is a meaningful sandbox signal. Network failures
// are different: a plain unreachable host (VPN, DNS, ECONNREFUSED) is not a
// sandbox, so require a permission-like error before hinting at a sandbox.
if (capability === "browser-launch" || capability === "localhost-bind") {
Comment thread
rafa-thayto marked this conversation as resolved.
return true;
}

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sandbox-hint-network-failures.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Stop showing the "possible sandboxed run" hint for ordinary network failures (unreachable host, VPN, DNS) in agent mode. The hint now requires a permission-like error before suggesting a sandbox, and is a single line.
59 changes: 59 additions & 0 deletions packages/cli-core/src/lib/host-execution.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
import { observeHostCapabilityFailure, _resetAgentHostStateProbe } from "./host-execution.ts";
import { setMode } from "../mode.ts";
import { useCaptureLog } from "../test/lib/stubs.ts";

describe("observeHostCapabilityFailure sandbox hinting", () => {
const captured = useCaptureLog();

beforeEach(() => {
setMode("agent");
_resetAgentHostStateProbe();
});

afterEach(() => {
setMode("human");
_resetAgentHostStateProbe();
});

test("a plain connectivity failure (unreachable host, VPN, DNS) does not warn", () => {
observeHostCapabilityFailure("network", new Error("ECONNREFUSED 127.0.0.1:443"));
expect(captured.err).not.toContain("sandboxed run");
});

test("a permission-like network failure warns about a possible sandbox (message)", () => {
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with permission-like code warns about a possible sandbox", () => {
const cause = Object.assign(new Error("operation not permitted"), { code: "EPERM" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with non-permission code does not warn", () => {
const cause = Object.assign(new Error("Connection refused"), { code: "ConnectionRefused" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).not.toContain("sandboxed run");
});

test("browser-launch and localhost-bind failures always warn", () => {
observeHostCapabilityFailure("browser-launch", new Error("spawn ENOENT"));
expect(captured.err).toContain("sandboxed run");
});

test("the hint is a single line", () => {
observeHostCapabilityFailure("network", new Error("EPERM"));
const warnLines = captured.err.split("\n").filter((line) => line.includes("agent mode"));
expect(warnLines).toHaveLength(1);
});

test("does not warn in human mode", () => {
setMode("human");
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).not.toContain("sandboxed run");
});
});
51 changes: 32 additions & 19 deletions packages/cli-core/src/lib/host-execution.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,36 +93,49 @@ function warnAboutSandbox(detail?: string): void {
warnedAboutSandbox = true;

log.warn(
"Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.",
);
log.warn(
"Re-run this command on the host shell before trusting auth, link, env, or API failures.",
"Host-only Clerk state or capabilities may be unavailable in agent mode (possible sandboxed run). If this looks wrong, re-run on the host shell before trusting auth, link, env, or API failures.",
);

if (detail) {
log.debug(detail);
}
}

const PERMISSION_PATTERNS = [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
];

function matchesPermissionPattern(s: string): boolean {
return PERMISSION_PATTERNS.some((pattern) => pattern.test(s));
}

function isPermissionLikeFailure(error: unknown): boolean {
const message = errorMessage(error);
return [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
].some((pattern) => pattern.test(message));
if (!(error instanceof Error)) {
return matchesPermissionPattern(String(error));
}

if (matchesPermissionPattern(error.message)) return true;

const code = (error as NodeJS.ErrnoException).code;
if (code && matchesPermissionPattern(code)) return true;

if (error.cause instanceof Error && isPermissionLikeFailure(error.cause)) return true;

return false;
}

function isLikelySandboxFailure(capability: HostCapability, error: unknown): boolean {
if (
capability === "network" ||
capability === "browser-launch" ||
capability === "localhost-bind"
) {
// browser-launch and localhost-bind only ever fail for host-capability
// reasons, so any failure is a meaningful sandbox signal. Network failures
// are different: a plain unreachable host (VPN, DNS, ECONNREFUSED) is not a
// sandbox, so require a permission-like error before hinting at a sandbox.
if (capability === "browser-launch" || capability === "localhost-bind") {
Comment thread
rafa-thayto marked this conversation as resolved.
return true;
}

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sandbox-hint-network-failures.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Stop showing the "possible sandboxed run" hint for ordinary network failures (unreachable host, VPN, DNS) in agent mode. The hint now requires a permission-like error before suggesting a sandbox, and is a single line.
59 changes: 59 additions & 0 deletions packages/cli-core/src/lib/host-execution.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
import { observeHostCapabilityFailure, _resetAgentHostStateProbe } from "./host-execution.ts";
import { setMode } from "../mode.ts";
import { useCaptureLog } from "../test/lib/stubs.ts";

describe("observeHostCapabilityFailure sandbox hinting", () => {
const captured = useCaptureLog();

beforeEach(() => {
setMode("agent");
_resetAgentHostStateProbe();
});

afterEach(() => {
setMode("human");
_resetAgentHostStateProbe();
});

test("a plain connectivity failure (unreachable host, VPN, DNS) does not warn", () => {
observeHostCapabilityFailure("network", new Error("ECONNREFUSED 127.0.0.1:443"));
expect(captured.err).not.toContain("sandboxed run");
});

test("a permission-like network failure warns about a possible sandbox (message)", () => {
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with permission-like code warns about a possible sandbox", () => {
const cause = Object.assign(new Error("operation not permitted"), { code: "EPERM" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with non-permission code does not warn", () => {
const cause = Object.assign(new Error("Connection refused"), { code: "ConnectionRefused" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).not.toContain("sandboxed run");
});

test("browser-launch and localhost-bind failures always warn", () => {
observeHostCapabilityFailure("browser-launch", new Error("spawn ENOENT"));
expect(captured.err).toContain("sandboxed run");
});

test("the hint is a single line", () => {
observeHostCapabilityFailure("network", new Error("EPERM"));
const warnLines = captured.err.split("\n").filter((line) => line.includes("agent mode"));
expect(warnLines).toHaveLength(1);
});

test("does not warn in human mode", () => {
setMode("human");
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).not.toContain("sandboxed run");
});
});
51 changes: 32 additions & 19 deletions packages/cli-core/src/lib/host-execution.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,36 +93,49 @@ function warnAboutSandbox(detail?: string): void {
warnedAboutSandbox = true;

log.warn(
"Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.",
);
log.warn(
"Re-run this command on the host shell before trusting auth, link, env, or API failures.",
"Host-only Clerk state or capabilities may be unavailable in agent mode (possible sandboxed run). If this looks wrong, re-run on the host shell before trusting auth, link, env, or API failures.",
);

if (detail) {
log.debug(detail);
}
}

const PERMISSION_PATTERNS = [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
];

function matchesPermissionPattern(s: string): boolean {
return PERMISSION_PATTERNS.some((pattern) => pattern.test(s));
}

function isPermissionLikeFailure(error: unknown): boolean {
const message = errorMessage(error);
return [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
].some((pattern) => pattern.test(message));
if (!(error instanceof Error)) {
return matchesPermissionPattern(String(error));
}

if (matchesPermissionPattern(error.message)) return true;

const code = (error as NodeJS.ErrnoException).code;
if (code && matchesPermissionPattern(code)) return true;

if (error.cause instanceof Error && isPermissionLikeFailure(error.cause)) return true;

return false;
}

function isLikelySandboxFailure(capability: HostCapability, error: unknown): boolean {
if (
capability === "network" ||
capability === "browser-launch" ||
capability === "localhost-bind"
) {
// browser-launch and localhost-bind only ever fail for host-capability
// reasons, so any failure is a meaningful sandbox signal. Network failures
// are different: a plain unreachable host (VPN, DNS, ECONNREFUSED) is not a
// sandbox, so require a permission-like error before hinting at a sandbox.
if (capability === "browser-launch" || capability === "localhost-bind") {
Comment thread
rafa-thayto marked this conversation as resolved.
return true;
}

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sandbox-hint-network-failures.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Stop showing the "possible sandboxed run" hint for ordinary network failures (unreachable host, VPN, DNS) in agent mode. The hint now requires a permission-like error before suggesting a sandbox, and is a single line.
59 changes: 59 additions & 0 deletions packages/cli-core/src/lib/host-execution.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
import { observeHostCapabilityFailure, _resetAgentHostStateProbe } from "./host-execution.ts";
import { setMode } from "../mode.ts";
import { useCaptureLog } from "../test/lib/stubs.ts";

describe("observeHostCapabilityFailure sandbox hinting", () => {
const captured = useCaptureLog();

beforeEach(() => {
setMode("agent");
_resetAgentHostStateProbe();
});

afterEach(() => {
setMode("human");
_resetAgentHostStateProbe();
});

test("a plain connectivity failure (unreachable host, VPN, DNS) does not warn", () => {
observeHostCapabilityFailure("network", new Error("ECONNREFUSED 127.0.0.1:443"));
expect(captured.err).not.toContain("sandboxed run");
});

test("a permission-like network failure warns about a possible sandbox (message)", () => {
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with permission-like code warns about a possible sandbox", () => {
const cause = Object.assign(new Error("operation not permitted"), { code: "EPERM" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with non-permission code does not warn", () => {
const cause = Object.assign(new Error("Connection refused"), { code: "ConnectionRefused" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).not.toContain("sandboxed run");
});

test("browser-launch and localhost-bind failures always warn", () => {
observeHostCapabilityFailure("browser-launch", new Error("spawn ENOENT"));
expect(captured.err).toContain("sandboxed run");
});

test("the hint is a single line", () => {
observeHostCapabilityFailure("network", new Error("EPERM"));
const warnLines = captured.err.split("\n").filter((line) => line.includes("agent mode"));
expect(warnLines).toHaveLength(1);
});

test("does not warn in human mode", () => {
setMode("human");
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).not.toContain("sandboxed run");
});
});
51 changes: 32 additions & 19 deletions packages/cli-core/src/lib/host-execution.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,36 +93,49 @@ function warnAboutSandbox(detail?: string): void {
warnedAboutSandbox = true;

log.warn(
"Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.",
);
log.warn(
"Re-run this command on the host shell before trusting auth, link, env, or API failures.",
"Host-only Clerk state or capabilities may be unavailable in agent mode (possible sandboxed run). If this looks wrong, re-run on the host shell before trusting auth, link, env, or API failures.",
);

if (detail) {
log.debug(detail);
}
}

const PERMISSION_PATTERNS = [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
];

function matchesPermissionPattern(s: string): boolean {
return PERMISSION_PATTERNS.some((pattern) => pattern.test(s));
}

function isPermissionLikeFailure(error: unknown): boolean {
const message = errorMessage(error);
return [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
].some((pattern) => pattern.test(message));
if (!(error instanceof Error)) {
return matchesPermissionPattern(String(error));
}

if (matchesPermissionPattern(error.message)) return true;

const code = (error as NodeJS.ErrnoException).code;
if (code && matchesPermissionPattern(code)) return true;

if (error.cause instanceof Error && isPermissionLikeFailure(error.cause)) return true;

return false;
}

function isLikelySandboxFailure(capability: HostCapability, error: unknown): boolean {
if (
capability === "network" ||
capability === "browser-launch" ||
capability === "localhost-bind"
) {
// browser-launch and localhost-bind only ever fail for host-capability
// reasons, so any failure is a meaningful sandbox signal. Network failures
// are different: a plain unreachable host (VPN, DNS, ECONNREFUSED) is not a
// sandbox, so require a permission-like error before hinting at a sandbox.
if (capability === "browser-launch" || capability === "localhost-bind") {
Comment thread
rafa-thayto marked this conversation as resolved.
return true;
}

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sandbox-hint-network-failures.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Stop showing the "possible sandboxed run" hint for ordinary network failures (unreachable host, VPN, DNS) in agent mode. The hint now requires a permission-like error before suggesting a sandbox, and is a single line.
59 changes: 59 additions & 0 deletions packages/cli-core/src/lib/host-execution.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
import { observeHostCapabilityFailure, _resetAgentHostStateProbe } from "./host-execution.ts";
import { setMode } from "../mode.ts";
import { useCaptureLog } from "../test/lib/stubs.ts";

describe("observeHostCapabilityFailure sandbox hinting", () => {
const captured = useCaptureLog();

beforeEach(() => {
setMode("agent");
_resetAgentHostStateProbe();
});

afterEach(() => {
setMode("human");
_resetAgentHostStateProbe();
});

test("a plain connectivity failure (unreachable host, VPN, DNS) does not warn", () => {
observeHostCapabilityFailure("network", new Error("ECONNREFUSED 127.0.0.1:443"));
expect(captured.err).not.toContain("sandboxed run");
});

test("a permission-like network failure warns about a possible sandbox (message)", () => {
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with permission-like code warns about a possible sandbox", () => {
const cause = Object.assign(new Error("operation not permitted"), { code: "EPERM" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with non-permission code does not warn", () => {
const cause = Object.assign(new Error("Connection refused"), { code: "ConnectionRefused" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).not.toContain("sandboxed run");
});

test("browser-launch and localhost-bind failures always warn", () => {
observeHostCapabilityFailure("browser-launch", new Error("spawn ENOENT"));
expect(captured.err).toContain("sandboxed run");
});

test("the hint is a single line", () => {
observeHostCapabilityFailure("network", new Error("EPERM"));
const warnLines = captured.err.split("\n").filter((line) => line.includes("agent mode"));
expect(warnLines).toHaveLength(1);
});

test("does not warn in human mode", () => {
setMode("human");
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).not.toContain("sandboxed run");
});
});
51 changes: 32 additions & 19 deletions packages/cli-core/src/lib/host-execution.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,36 +93,49 @@ function warnAboutSandbox(detail?: string): void {
warnedAboutSandbox = true;

log.warn(
"Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.",
);
log.warn(
"Re-run this command on the host shell before trusting auth, link, env, or API failures.",
"Host-only Clerk state or capabilities may be unavailable in agent mode (possible sandboxed run). If this looks wrong, re-run on the host shell before trusting auth, link, env, or API failures.",
);

if (detail) {
log.debug(detail);
}
}

const PERMISSION_PATTERNS = [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
];

function matchesPermissionPattern(s: string): boolean {
return PERMISSION_PATTERNS.some((pattern) => pattern.test(s));
}

function isPermissionLikeFailure(error: unknown): boolean {
const message = errorMessage(error);
return [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
].some((pattern) => pattern.test(message));
if (!(error instanceof Error)) {
return matchesPermissionPattern(String(error));
}

if (matchesPermissionPattern(error.message)) return true;

const code = (error as NodeJS.ErrnoException).code;
if (code && matchesPermissionPattern(code)) return true;

if (error.cause instanceof Error && isPermissionLikeFailure(error.cause)) return true;

return false;
}

function isLikelySandboxFailure(capability: HostCapability, error: unknown): boolean {
if (
capability === "network" ||
capability === "browser-launch" ||
capability === "localhost-bind"
) {
// browser-launch and localhost-bind only ever fail for host-capability
// reasons, so any failure is a meaningful sandbox signal. Network failures
// are different: a plain unreachable host (VPN, DNS, ECONNREFUSED) is not a
// sandbox, so require a permission-like error before hinting at a sandbox.
if (capability === "browser-launch" || capability === "localhost-bind") {
Comment thread
rafa-thayto marked this conversation as resolved.
return true;
}

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sandbox-hint-network-failures.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Stop showing the "possible sandboxed run" hint for ordinary network failures (unreachable host, VPN, DNS) in agent mode. The hint now requires a permission-like error before suggesting a sandbox, and is a single line.
59 changes: 59 additions & 0 deletions packages/cli-core/src/lib/host-execution.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
import { observeHostCapabilityFailure, _resetAgentHostStateProbe } from "./host-execution.ts";
import { setMode } from "../mode.ts";
import { useCaptureLog } from "../test/lib/stubs.ts";

describe("observeHostCapabilityFailure sandbox hinting", () => {
const captured = useCaptureLog();

beforeEach(() => {
setMode("agent");
_resetAgentHostStateProbe();
});

afterEach(() => {
setMode("human");
_resetAgentHostStateProbe();
});

test("a plain connectivity failure (unreachable host, VPN, DNS) does not warn", () => {
observeHostCapabilityFailure("network", new Error("ECONNREFUSED 127.0.0.1:443"));
expect(captured.err).not.toContain("sandboxed run");
});

test("a permission-like network failure warns about a possible sandbox (message)", () => {
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with permission-like code warns about a possible sandbox", () => {
const cause = Object.assign(new Error("operation not permitted"), { code: "EPERM" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with non-permission code does not warn", () => {
const cause = Object.assign(new Error("Connection refused"), { code: "ConnectionRefused" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).not.toContain("sandboxed run");
});

test("browser-launch and localhost-bind failures always warn", () => {
observeHostCapabilityFailure("browser-launch", new Error("spawn ENOENT"));
expect(captured.err).toContain("sandboxed run");
});

test("the hint is a single line", () => {
observeHostCapabilityFailure("network", new Error("EPERM"));
const warnLines = captured.err.split("\n").filter((line) => line.includes("agent mode"));
expect(warnLines).toHaveLength(1);
});

test("does not warn in human mode", () => {
setMode("human");
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).not.toContain("sandboxed run");
});
});
51 changes: 32 additions & 19 deletions packages/cli-core/src/lib/host-execution.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,36 +93,49 @@ function warnAboutSandbox(detail?: string): void {
warnedAboutSandbox = true;

log.warn(
"Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.",
);
log.warn(
"Re-run this command on the host shell before trusting auth, link, env, or API failures.",
"Host-only Clerk state or capabilities may be unavailable in agent mode (possible sandboxed run). If this looks wrong, re-run on the host shell before trusting auth, link, env, or API failures.",
);

if (detail) {
log.debug(detail);
}
}

const PERMISSION_PATTERNS = [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
];

function matchesPermissionPattern(s: string): boolean {
return PERMISSION_PATTERNS.some((pattern) => pattern.test(s));
}

function isPermissionLikeFailure(error: unknown): boolean {
const message = errorMessage(error);
return [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
].some((pattern) => pattern.test(message));
if (!(error instanceof Error)) {
return matchesPermissionPattern(String(error));
}

if (matchesPermissionPattern(error.message)) return true;

const code = (error as NodeJS.ErrnoException).code;
if (code && matchesPermissionPattern(code)) return true;

if (error.cause instanceof Error && isPermissionLikeFailure(error.cause)) return true;

return false;
}

function isLikelySandboxFailure(capability: HostCapability, error: unknown): boolean {
if (
capability === "network" ||
capability === "browser-launch" ||
capability === "localhost-bind"
) {
// browser-launch and localhost-bind only ever fail for host-capability
// reasons, so any failure is a meaningful sandbox signal. Network failures
// are different: a plain unreachable host (VPN, DNS, ECONNREFUSED) is not a
// sandbox, so require a permission-like error before hinting at a sandbox.
if (capability === "browser-launch" || capability === "localhost-bind") {
Comment thread
rafa-thayto marked this conversation as resolved.
return true;
}

Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sandbox-hint-network-failures.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"clerk": patch
---

Stop showing the "possible sandboxed run" hint for ordinary network failures (unreachable host, VPN, DNS) in agent mode. The hint now requires a permission-like error before suggesting a sandbox, and is a single line.
59 changes: 59 additions & 0 deletions packages/cli-core/src/lib/host-execution.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
import { test, expect, describe, beforeEach, afterEach } from "bun:test";
import { observeHostCapabilityFailure, _resetAgentHostStateProbe } from "./host-execution.ts";
import { setMode } from "../mode.ts";
import { useCaptureLog } from "../test/lib/stubs.ts";

describe("observeHostCapabilityFailure sandbox hinting", () => {
const captured = useCaptureLog();

beforeEach(() => {
setMode("agent");
_resetAgentHostStateProbe();
});

afterEach(() => {
setMode("human");
_resetAgentHostStateProbe();
});

test("a plain connectivity failure (unreachable host, VPN, DNS) does not warn", () => {
observeHostCapabilityFailure("network", new Error("ECONNREFUSED 127.0.0.1:443"));
expect(captured.err).not.toContain("sandboxed run");
});

test("a permission-like network failure warns about a possible sandbox (message)", () => {
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with permission-like code warns about a possible sandbox", () => {
const cause = Object.assign(new Error("operation not permitted"), { code: "EPERM" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).toContain("sandboxed run");
});

test("a Bun fetch error with non-permission code does not warn", () => {
const cause = Object.assign(new Error("Connection refused"), { code: "ConnectionRefused" });
const bunFetchError = Object.assign(new Error("fetch failed"), { cause });
observeHostCapabilityFailure("network", bunFetchError);
expect(captured.err).not.toContain("sandboxed run");
});

test("browser-launch and localhost-bind failures always warn", () => {
observeHostCapabilityFailure("browser-launch", new Error("spawn ENOENT"));
expect(captured.err).toContain("sandboxed run");
});

test("the hint is a single line", () => {
observeHostCapabilityFailure("network", new Error("EPERM"));
const warnLines = captured.err.split("\n").filter((line) => line.includes("agent mode"));
expect(warnLines).toHaveLength(1);
});

test("does not warn in human mode", () => {
setMode("human");
observeHostCapabilityFailure("network", new Error("operation not permitted"));
expect(captured.err).not.toContain("sandboxed run");
});
});
51 changes: 32 additions & 19 deletions packages/cli-core/src/lib/host-execution.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,36 +93,49 @@ function warnAboutSandbox(detail?: string): void {
warnedAboutSandbox = true;

log.warn(
"Host-only Clerk state or system capabilities may be unavailable in agent mode. This may be a sandboxed run.",
);
log.warn(
"Re-run this command on the host shell before trusting auth, link, env, or API failures.",
"Host-only Clerk state or capabilities may be unavailable in agent mode (possible sandboxed run). If this looks wrong, re-run on the host shell before trusting auth, link, env, or API failures.",
);

if (detail) {
log.debug(detail);
}
}

const PERMISSION_PATTERNS = [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
];

function matchesPermissionPattern(s: string): boolean {
return PERMISSION_PATTERNS.some((pattern) => pattern.test(s));
}

function isPermissionLikeFailure(error: unknown): boolean {
const message = errorMessage(error);
return [
/\bEPERM\b/i,
/\bEACCES\b/i,
/operation not permitted/i,
/permission denied/i,
/sandbox/i,
/interaction is not allowed/i,
/access denied/i,
].some((pattern) => pattern.test(message));
if (!(error instanceof Error)) {
return matchesPermissionPattern(String(error));
}

if (matchesPermissionPattern(error.message)) return true;

const code = (error as NodeJS.ErrnoException).code;
if (code && matchesPermissionPattern(code)) return true;

if (error.cause instanceof Error && isPermissionLikeFailure(error.cause)) return true;

return false;
}

function isLikelySandboxFailure(capability: HostCapability, error: unknown): boolean {
if (
capability === "network" ||
capability === "browser-launch" ||
capability === "localhost-bind"
) {
// browser-launch and localhost-bind only ever fail for host-capability
// reasons, so any failure is a meaningful sandbox signal. Network failures
// are different: a plain unreachable host (VPN, DNS, ECONNREFUSED) is not a
// sandbox, so require a permission-like error before hinting at a sandbox.
if (capability === "browser-launch" || capability === "localhost-bind") {
Comment thread
rafa-thayto marked this conversation as resolved.
return true;
}

Expand Down