odysseus-dev/odysseus · error · Error

Import failed

Error message

Import failed

What it means

Fallback message when a contacts import fails without a specific error. Files are read, split into vcf/csv parts by content sniffing, POSTed to /api/contacts/import, and any thrown error is shown as err?.message || 'Import failed'. Note the unguarded r.json() inside _postImport: a non-JSON response rejects before the d.error check, yielding a confusing SyntaxError message rather than 'Import failed'.

Source

Thrown at static/js/settings.js:4162

      if (btn) { btn.textContent = 'Importing…'; btn.disabled = true; }
      try {
        const texts = await Promise.all(files.map(f => f.text()));
        const vcfParts = [];
        const csvParts = [];
        texts.forEach((text, idx) => {
          const name = (files[idx]?.name || '').toLowerCase();
          if (name.endsWith('.csv') || !String(text || '').toUpperCase().includes('BEGIN:VCARD')) csvParts.push(text);
          else vcfParts.push(text);
        });
        let imported = 0, total = 0, failed = 0;
        const _postImport = async (body) => {
          const r = await fetch('/api/contacts/import', {
            method: 'POST', credentials: 'same-origin',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(body),
          });
          const d = await r.json();
          if (d.error) throw new Error(d.error);
          imported += Number(d.imported || 0);
          total += Number(d.total || 0);
          failed += Number(d.failed || 0);
        };
        if (vcfParts.length) await _postImport({ vcf: vcfParts.join('\n') });
        if (csvParts.length) await _postImport({ csv: csvParts.join('\n') });
        if (!vcfParts.length && !csvParts.length) throw new Error('No contact data found');
        const msg = `Imported ${imported}/${total}` + (failed ? ` (${failed} failed)` : '');
        uiModule.showToast ? uiModule.showToast(msg) : null;
      } catch (err) {
        uiModule.showError ? uiModule.showError(err?.message || 'Import failed') : alert(err?.message || 'Import failed');
      } finally {
        if (btn) { btn.textContent = orig; btn.disabled = false; }
        e.target.value = '';
        await _renderContactsManager();
      }
    });
    await _renderContactsManager();

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Check the import POST response in devtools — d.error carries the server-side parse reason
  2. Clean the file: UTF-8, proper .vcf/.csv content, no stray rows
  3. Raise any proxy body-size limit for large imports
  4. Guard r.json() with .catch and check r.ok explicitly so failures produce clear messages

Example fix

// before
          const d = await r.json();
          if (d.error) throw new Error(d.error);

// after
          const d = await r.json().catch(() => null);
          if (!r.ok || !d) throw new Error((d && d.error) || `Import failed (HTTP ${r.status})`);
          if (d.error) throw new Error(d.error);
Defensive patterns

Strategy: try-catch

Validate before calling

const files = Array.from(e.target.files || []);
if (!files.length) return;
const texts = await Promise.all(files.map(f => f.text().catch(() => '')));
if (!texts.some(t => t.trim())) throw new Error('No contact data found');

Try / catch

try { const r = await fetch('/api/contacts/import', {...}); const d = await r.json().catch(() => null); if (!r.ok || !d) throw new Error((d && d.error) || `Import failed (HTTP ${r.status})`); if (d.error) throw new Error(d.error); ... } catch (err) { (uiModule.showError || alert)(err?.message || 'Import failed'); }

Prevention

When it happens

Trigger: POST /api/contacts/import returns an error body ({error: ...}) or a non-JSON body (HTML error page, 502 from a proxy) making r.json() throw; malformed vCard/CSV rows failing server-side parsing; oversized upload rejected; network failure mid-import.

Common situations: Importing a .csv that is actually Excel-exported with BOM/encoding issues; vCard version quirks (3.0 vs 4.0 folded lines); proxy body-size limits; expired auth; the err?.message fallback rarely fires because r.json() failures produce their own messages.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/6aba76652f09b325. Report an issue: GitHub.