jackwener/OpenCLI · error · CommandExecutionError

Upwork result at rank ${rank} did not include a valid cipher

Error message

Upwork result at rank ${rank} did not include a valid ciphertext id; cannot produce round-trippable detail rows.

What it means

jobsToListRows maps raw Upwork job results to round-trippable list rows and throws CommandExecutionError when a job at a given rank yields no row via jobToListRow — meaning the payload had no valid ciphertext id, so the detail command could never re-open that job. This fails fast rather than emitting rows that break the id round-trip contract.

Source

Thrown at clis/upwork/utils.js:305

        budget: formatBudget(job),
        experienceLevel: decodeExperienceLevel(job?.tierText ?? job?.tier),
        proposalsTier: decodeProposalsTier(job?.proposalsTier),
        skills: formatSkills(job),
        clientCountry: country,
        clientRating: Number.isFinite(rating) && rating > 0 ? rating : null,
        publishedOn: job?.publishedOn || job?.createdOn || '',
        url: buildJobUrl(id),
    };
}

export function jobsToListRows(jobs, { offset = 0, limit } = {}) {
    const rows = [];
    const source = limit ? jobs.slice(0, limit) : jobs;
    for (const [index, job] of source.entries()) {
        const rank = offset + index + 1;
        const row = jobToListRow(job, rank);
        if (!row) {
            throw new CommandExecutionError(`Upwork result at rank ${rank} did not include a valid ciphertext id; cannot produce round-trippable detail rows.`);
        }
        rows.push(row);
    }
    return rows;
}

export const LIST_COLUMNS = [
    'rank', 'id', 'title', 'type', 'budget',
    'experienceLevel', 'proposalsTier', 'skills',
    'clientCountry', 'clientRating', 'publishedOn', 'url',
];

export const DETAIL_COLUMNS = [
    'id', 'title', 'type', 'budget', 'experienceLevel', 'workload',
    'category', 'skills', 'description',
    'clientCountry', 'clientSpent', 'clientHires', 'clientRating',
    'proposalsCount', 'publishedOn', 'url',
];

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command — transient ad/removed entries often disappear
  2. Verify you are logged in (session cookie valid); anonymous payloads are degraded
  3. Narrow filters or reduce the limit to skip the problematic rank
  4. Upgrade the adapter if Upwork changed the payload shape
  5. Report the rank/URL so jobToListRow can be taught to skip such entries
Defensive patterns

Strategy: try-catch

Validate before calling

function jobsHaveIds(jobs) {
  return jobs.every(j => j && typeof j === 'object' && typeof (j.id ?? j.ciphertext) === 'string' && /^~0[12]/.test(j.id ?? j.ciphertext));
}

Type guard

function hasValidCiphertext(job) {
  const id = job?.id ?? job?.ciphertext;
  return typeof id === 'string' && /^~0[12]\d{15,21}$/.test(id);
}

Try / catch

try {
  rows = jobsToListRows(jobs, { limit, offset });
} catch (e) {
  if (e instanceof CommandExecutionError && /did not include a valid ciphertext/.test(e.message)) {
    // drop the bad entry and retry, or warn and continue
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: Upwork's __NUXT__ payload contains a job entry missing or malformed ciphertext id (e.g. promoted/ad cards, removed jobs still in the payload, partially rendered SSR state); scraping/structural changes to the payload shape.

Common situations: Upwork A/B tests or layout changes injecting non-job cards into results; jobs unpublished between listing and rendering; Cloudflare-challenge pages or logged-out sessions yielding degraded payloads; ad slots appearing in 'most-recent' feed.

Related errors


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