jackwener/OpenCLI · error · ArgumentError

upwork job id "${value}" is not a valid ciphertext (expected

Error message

upwork job id "${value}" is not a valid ciphertext (expected ~01… or ~02… followed by digits)

What it means

requireCiphertext throws this when the provided id (after trimming and extracting a ~0[12]\d+ substring from a possible full URL) does not match CIPHERTEXT_PATTERN /^~0[12]\d{15,21}$/ — i.e. it is not a valid ~01/~02 ciphertext of 16-22 total characters.

Source

Thrown at clis/upwork/utils.js:83

export function requireBoundedInt(value, defaultValue, min, max, label) {
    const n = requirePositiveInt(value, defaultValue, label);
    if (n < min) throw new ArgumentError(`upwork ${label} must be >= ${min}`);
    if (n > max) throw new ArgumentError(`upwork ${label} must be <= ${max}`);
    return n;
}

/**
 * Upwork job ids are the ciphertext form starting with `~01` or `~02`
 * (the encoded uid surfaced everywhere in URLs and search results).
 * Accepts a bare ciphertext or a full `/jobs/~02…` URL.
 */
export function requireCiphertext(value) {
    let id = String(value ?? '').trim();
    if (!id) throw new ArgumentError('upwork job id is required');
    const urlMatch = id.match(/~0[12]\d+/);
    if (urlMatch) id = urlMatch[0];
    if (!CIPHERTEXT_PATTERN.test(id)) {
        throw new ArgumentError(`upwork job id "${value}" is not a valid ciphertext (expected ~01… or ~02… followed by digits)`);
    }
    return id;
}

export function requireFeedTab(value, defaultValue = 'best-matches') {
    const v = String(value ?? defaultValue).trim().toLowerCase();
    if (!FEED_TABS[v]) {
        throw new ArgumentError(`upwork tab must be one of ${Object.keys(FEED_TABS).join(' / ')}, got "${value}"`);
    }
    return v;
}

export function requireSort(value, defaultValue = 'recency') {
    const v = String(value ?? defaultValue).trim().toLowerCase();
    if (!SORT_VALUES.has(v)) {
        throw new ArgumentError(`upwork sort must be one of ${Array.from(SORT_VALUES).join(' / ')}, got "${value}"`);
    }
    return v;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Copy the full ciphertext id starting with ~01 or ~02 from the job URL or search output
  2. If you have a job URL, pass the whole URL — the helper extracts the ciphertext automatically
  3. Check for truncation (needs ~ + '0' + '1'/'2' + 15-21 digits)
  4. Re-run the search/list command to obtain a fresh round-trippable id

Example fix

// before
await show('17483920'); // numeric uid -> not valid ciphertext
// after
await show('~01a8f3c2d94b7e610253');
Defensive patterns

Strategy: validation

Validate before calling

const CIPHERTEXT = /^~0[12]\d{15,21}$/;
function looksLikeCiphertext(v) {
  if (typeof v !== 'string') return false;
  const m = v.match(/~0[12]\d+/);
  return !!m && CIPHERTEXT.test(m[0]);
}
if (!looksLikeCiphertext(id)) throw new Error('not a ~01/~02 ciphertext id');

Type guard

function isJobCiphertext(v) {
  return typeof v === 'string' && /^~0[12]\d{15,21}$/.test(v.trim().match(/~0[12]\d+/)?.[0] ?? '');
}

Try / catch

try {
  await show(id);
} catch (e) {
  if (e instanceof ArgumentError && /not a valid ciphertext/.test(e.message)) {
    console.error('Use the ~01…/~02… id from a job URL or list output');
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a numeric Upwork uid instead of the ciphertext form; passing a truncated or corrupted id (too few digits); passing a URL whose id does not match the pattern; passing an opaque job reference from an old export.

Common situations: Using the internal numeric job id from the API instead of the ciphertext from URLs/search results; copy-paste truncation dropping the `~` or digits; older tooling that stored a different id format.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/9cee36ad188e2184. Report an issue: GitHub.