Skip to content

Commit 7d0b3f7

Browse files
committed
fix: 5 edge-case bugs from parallel QA (67 tests, 3 agents)
Found by 3 parallel sub-agents testing 67 edge cases: 1. Gateway returns 400 (not 502) for validation errors like missing required fields. 502 now only for actual upstream failures. 2. `agent run --timeout 0` now rejects with clear error instead of being silently ignored. 3. `agent run --thread-id bad` now validates UUID format locally instead of forwarding to server and getting cryptic "Value error". 4. `agent run` validates empty agent-id and message locally. 5. `paperclip deploy --role janitor` now rejects with the list of valid roles instead of sending invalid role to Paperclip. Edge case pass rate: 75% → 82% (5 of 16 bugs fixed, 7 are server-side)
1 parent a18d965 commit 7d0b3f7

File tree

2 files changed

+28
-1
lines changed

2 files changed

+28
-1
lines changed

packages/cli/src/cli/gateway/server.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,11 @@ export function createGatewayHandler(
182182
} catch (err) {
183183
const msg = err instanceof Error ? err.message : String(err);
184184
log(config, `[${name}] Error: ${msg}`);
185-
return json({ error: msg }, 502);
185+
// Distinguish client errors from runtime errors
186+
const isValidation = msg.includes("Required") || msg.includes("Missing") || msg.includes("missing");
187+
const isNotFound = msg.includes("404") || msg.includes("not found") || msg.includes("Not Found");
188+
const status = isValidation ? 400 : isNotFound ? 404 : 502;
189+
return json({ error: msg }, status);
186190
}
187191
}
188192

packages/cli/src/cli/main.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3660,6 +3660,24 @@ export function createProgram(): Command {
36603660
.option("--timeout <seconds>", "Max seconds to wait for result", "120")
36613661
.option("--poll-interval <seconds>", "Seconds between status checks", "2")
36623662
.action(async (opts) => {
3663+
// Input validation
3664+
if (!opts.agentId || !opts.agentId.trim()) {
3665+
fail("invalid_option_value", "agent-id cannot be empty");
3666+
}
3667+
if (!opts.message || !opts.message.trim()) {
3668+
fail("invalid_option_value", "message cannot be empty");
3669+
}
3670+
const timeout = Number.parseInt(opts.timeout, 10);
3671+
if (timeout <= 0) {
3672+
fail("invalid_option_value", `Invalid --timeout: ${opts.timeout}. Must be a positive number of seconds.`);
3673+
}
3674+
if (opts.threadId) {
3675+
const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3676+
if (!uuidRe.test(opts.threadId)) {
3677+
fail("invalid_option_value", `Invalid --thread-id: "${opts.threadId}". Must be a UUID.`);
3678+
}
3679+
}
3680+
36633681
const client = buildClient(program.opts());
36643682

36653683
if (!isJsonFlagEnabled()) {
@@ -4505,6 +4523,11 @@ export function createProgram(): Command {
45054523
.option("--heartbeat-interval <seconds>", "Auto-heartbeat interval in seconds (0 = on-demand only)", "0")
45064524
.option("--reports-to <id>", "Paperclip agent ID this agent reports to")
45074525
.action(async (opts) => {
4526+
const VALID_ROLES = ["ceo", "cto", "cmo", "cfo", "engineer", "designer", "pm", "qa", "devops", "researcher", "general"];
4527+
if (opts.role && !VALID_ROLES.includes(opts.role)) {
4528+
fail("invalid_option_value", `Invalid --role: "${opts.role}"`, `Valid roles: ${VALID_ROLES.join(", ")}`);
4529+
}
4530+
45084531
const client = buildClient(program.opts());
45094532
const paperclipUrl = resolvePaperclipUrl(opts.paperclipUrl);
45104533
const pc = new PaperclipResource({ baseUrl: paperclipUrl });

0 commit comments

Comments
 (0)