jackwener/OpenCLI · error · CommandExecutionError

Upwork job-detail store returned ciphertext "${returnedCiphe

Error message

Upwork job-detail store returned ciphertext "${returnedCiphertext}" while reading "${id}".

What it means

After reading the job from the Vuex store, `upwork detail` verifies that the job's ciphertext field matches the id the caller asked for. This CommandExecutionError is thrown when the store returns a job whose ciphertext differs from the requested id — a consistency guard ensuring the CLI never shows details for the wrong job. It only fires when the store returned a non-empty ciphertext, so blank ciphertexts are tolerated.

Source

Thrown at clis/upwork/detail.js:101

            throw new AuthRequiredError('upwork.com', 'Upwork redirected to login. Open https://www.upwork.com in the connected browser and sign in, then retry.');
        }
        if (payload?.challenge) {
            throw new CommandExecutionError('Upwork served a Cloudflare challenge page', 'Open https://www.upwork.com in the connected browser and clear the challenge, then retry.');
        }
        if (!isPlainObject(payload)) {
            throw new CommandExecutionError('Upwork detail returned an unexpected Browser Bridge payload shape');
        }
        if (!payload?.ready || !payload.job) {
            throw new EmptyResultError('upwork detail', `No Upwork job posting found for id "${id}" (may be closed, expired, or private)`);
        }
        if (!isPlainObject(payload.job)) {
            throw new CommandExecutionError('Upwork job-detail store had an unexpected job shape; expected an object.');
        }

        const job = payload.job;
        const returnedCiphertext = String(job?.ciphertext ?? '').trim();
        if (returnedCiphertext && returnedCiphertext !== id) {
            throw new CommandExecutionError(`Upwork job-detail store returned ciphertext "${returnedCiphertext}" while reading "${id}".`);
        }
        const buyer = payload.buyer || {};
        const stats = buyer?.stats || {};
        const location = buyer?.location || {};
        const category = job?.category?.name || '';
        const skills = formatSkills(job);
        const totalSpent = Number(stats?.totalCharges?.amount);
        const totalHires = Number(stats?.totalJobsWithHires);
        const score = Number(stats?.score);
        const totalApplicants = Number(job?.clientActivity?.totalApplicants);

        return [{
            id,
            title: stripHighlight(job?.title),
            type: jobType(job?.type),
            budget: formatBudgetFromDetail(job),
            experienceLevel: decodeExperienceLevel(job?.contractorTier),
            workload: decodeWorkload(job?.workload),

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the id: copy it again from `upwork search`/`upwork feed` output or the job URL, ensuring the full ~01…/~02… ciphertext is intact.
  2. Reload the job page in the connected browser (hard navigate to the job URL) so the Vuex store resets to the requested job, then retry.
  3. Use a fresh browser tab/profile for the command so no stale jobDetails state lingers.
  4. If Upwork consistently redirects this id to a different ciphertext, use the canonical id from the redirect target.

Example fix

// before
await upwork.detail('02abc');            // truncated ciphertext
// after
await upwork.detail('~020145964136512093518'); // full ciphertext copied from feed output
Defensive patterns

Strategy: validation

Validate before calling

// validate the id before calling
const id = '~02' + digits; // copy the FULL ciphertext from feed/search output
if (!/^~0[0-9a-f]+$/i.test(id) || id.length < 10) throw new Error('Malformed Upwork ciphertext id');

Try / catch

try {
  const detail = await upwork.detail(id);
} catch (e) {
  if (/returned ciphertext .* while reading/.test(e.message)) {
    // the store was stale; reload the job page then retry with the exact id
    return refreshAndRetry(id);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `upwork detail <id>` where window.$nuxt.$store.state.jobDetails.job.ciphertext exists but !== the requested id — typically because Upwork pre-populated or left a stale job in the store from a previously viewed posting, a redirect remapped the id (e.g. ~01 to ~02 forms), or the URL resolved to a different/canonical job.

Common situations: Reusing a long-lived browser tab whose Vuex store still holds the previously viewed job; following a job link that Upwork 301-redirects to a canonical posting; passing a truncated or altered ciphertext id copied from search results; Upwork changing its ciphertext canonicalization.

Related errors


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