jackwener/OpenCLI · error · CommandExecutionError
Failed to attach file
Error message
Failed to attach file
What it means
sendWithFile() returned a failure result while attaching kwargs.file alongside the prompt; the library raises CommandExecutionError with the specific reason (or the generic 'Failed to attach file'). A thrown error is swallowed only if it matches 'Promise was collected', which actually indicates success.
Source
Thrown at clis/claude/ask.js:112
throw new CommandExecutionError(`Could not switch to ${wantModel} model`);
}
// Post-toggle settle dropped — the next CDP eval (setAdaptiveThinking) gives
// React enough time to flush aria-checked updates between rountrips.
}
const thinkResult = await withRetry(() => setAdaptiveThinking(page, wantThink));
if (!thinkResult?.ok && wantThink) {
throw new CommandExecutionError('Could not enable Adaptive thinking');
}
// Post-toggle settle dropped — the next CDP eval (sendMessage / sendWithFile)
// gives React enough time to flush aria-checked updates.
if (kwargs.file) {
const baseline = await withRetry(() => getBubbleCount(page));
try {
const fileResult = await sendWithFile(page, kwargs.file, prompt);
if (fileResult && !fileResult.ok) {
throw new CommandExecutionError(fileResult.reason || 'Failed to attach file');
}
} catch (err) {
// SPA navigates after send; "Promise was collected" means send succeeded
if (!String(err?.message || err).includes('Promise was collected')) throw err;
}
// Pre-waitForResponse settle dropped — waitForResponse's first 3 s polling
// tick covers the same window without an unconditional sleep.
const result = await waitForResponse(page, baseline, prompt, timeoutMs);
if (!result) {
throw new EmptyResultError(
'claude ask',
`No Claude response appeared within ${timeoutSeconds}s. Re-run with a higher --timeout if the model is still generating.`,
);
}
return [{ response: result }];
}
const baseline = await withRetry(() => getBubbleCount(page));View on GitHub (pinned to 49907e53dc)
Solutions
- Verify the --file path exists and the file type is supported (image, PDF, text).
- Retry the command — transient upload widget timing may resolve on a second run.
- Send the prompt without --file, or attach the file manually in the browser if automation keeps failing.
Example fix
// before opencli claude ask "describe" --file ./reprot.pdf # typo, file missing // after opencli claude ask "describe" --file ./report.pdf
Defensive patterns
Strategy: validation
Validate before calling
import { statSync } from 'fs';
const ALLOWED = /\.(png|jpe?g|gif|webp|pdf|txt|md)$/i;
function validateFile(p) {
const s = statSync(p); // throws if missing
if (s.size > 30 * 1024 * 1024) throw new Error('file too large for Claude upload');
if (!ALLOWED.test(p)) throw new Error('unsupported file type');
}
validateFile(opts.file); Try / catch
try {
return await opencli.claude.ask(prompt, { file: p, timeout: 300 });
} catch (e) {
if (/Failed to attach file|attach/i.test(e.message)) {
return await opencli.claude.ask(prompt); // degrade to text-only
}
throw e;
} Prevention
- Stat the --file path and check extension/size before invoking.
- Use supported types (images, PDF, text) within Claude's size limits.
- Retry once before giving up; upload widgets can be flaky.
When it happens
Trigger: `claude ask ... --file photo.png` where the file input/upload fails: nonexistent file path, unsupported type, upload rejected by the page, or fileResult.ok false for any reason.
Common situations: Typo in the --file path; attaching a file type Claude rejects; file too large; SPA upload widget not ready when the automation interacts with it.
Related errors
- Image injection failed: ${upload.error ?? 'unknown'}. Debug
- Appending images failed: ${upload.error ?? 'unknown'}. Debug
- Instagram fallback file injection failed
- Mercury reimbursement-draft requires Browser Bridge uploadFi
- Mercury receipt upload did not confirm exactly one uploaded
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/a99e706de05e8501.
Report an issue: GitHub.