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
- Verify the id: copy it again from `upwork search`/`upwork feed` output or the job URL, ensuring the full ~01…/~02… ciphertext is intact.
- 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.
- Use a fresh browser tab/profile for the command so no stale jobDetails state lingers.
- 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
- Always copy the full ~01…/~02… ciphertext; never truncate or hand-edit ids.
- Use a dedicated browser profile for the CLI so stale jobDetails state from manual browsing doesn't linger.
- Pipe ids directly from feed/search output instead of retyping them.
- If the error persists for one id, check whether Upwork redirects it to a canonical job and use that id.
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
- Upwork job-detail store had an unexpected job shape; expecte
- Upwork feed returned an unexpected Browser Bridge payload sh
- Upwork result at rank ${rank} did not include a valid cipher
- 该聊天缺少 securityId,无法获取历史消息
- Boss recruiter history response did not include a message li
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/7c0e3bda6faf3676.
Report an issue: GitHub.