{"record":{"id":"7c0e3bda6faf3676","repo":"jackwener/OpenCLI","slug":"upwork-job-detail-store-returned-ciphertext-ret","errorCode":null,"errorMessage":"Upwork job-detail store returned ciphertext \"${returnedCiphertext}\" while reading \"${id}\".","messagePattern":"Upwork job-detail store returned ciphertext \"(.+?)\" while reading \"(.+?)\"\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/upwork/detail.js","lineNumber":101,"sourceCode":"            throw new AuthRequiredError('upwork.com', 'Upwork redirected to login. Open https://www.upwork.com in the connected browser and sign in, then retry.');\n        }\n        if (payload?.challenge) {\n            throw new CommandExecutionError('Upwork served a Cloudflare challenge page', 'Open https://www.upwork.com in the connected browser and clear the challenge, then retry.');\n        }\n        if (!isPlainObject(payload)) {\n            throw new CommandExecutionError('Upwork detail returned an unexpected Browser Bridge payload shape');\n        }\n        if (!payload?.ready || !payload.job) {\n            throw new EmptyResultError('upwork detail', `No Upwork job posting found for id \"${id}\" (may be closed, expired, or private)`);\n        }\n        if (!isPlainObject(payload.job)) {\n            throw new CommandExecutionError('Upwork job-detail store had an unexpected job shape; expected an object.');\n        }\n\n        const job = payload.job;\n        const returnedCiphertext = String(job?.ciphertext ?? '').trim();\n        if (returnedCiphertext && returnedCiphertext !== id) {\n            throw new CommandExecutionError(`Upwork job-detail store returned ciphertext \"${returnedCiphertext}\" while reading \"${id}\".`);\n        }\n        const buyer = payload.buyer || {};\n        const stats = buyer?.stats || {};\n        const location = buyer?.location || {};\n        const category = job?.category?.name || '';\n        const skills = formatSkills(job);\n        const totalSpent = Number(stats?.totalCharges?.amount);\n        const totalHires = Number(stats?.totalJobsWithHires);\n        const score = Number(stats?.score);\n        const totalApplicants = Number(job?.clientActivity?.totalApplicants);\n\n        return [{\n            id,\n            title: stripHighlight(job?.title),\n            type: jobType(job?.type),\n            budget: formatBudgetFromDetail(job),\n            experienceLevel: decodeExperienceLevel(job?.contractorTier),\n            workload: decodeWorkload(job?.workload),","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/upwork/detail.js#L83-L119","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait upwork.detail('02abc');            // truncated ciphertext\n// after\nawait upwork.detail('~020145964136512093518'); // full ciphertext copied from feed output","handlingStrategy":"validation","validationCode":"// validate the id before calling\nconst id = '~02' + digits; // copy the FULL ciphertext from feed/search output\nif (!/^~0[0-9a-f]+$/i.test(id) || id.length < 10) throw new Error('Malformed Upwork ciphertext id');","typeGuard":null,"tryCatchPattern":"try {\n  const detail = await upwork.detail(id);\n} catch (e) {\n  if (/returned ciphertext .* while reading/.test(e.message)) {\n    // the store was stale; reload the job page then retry with the exact id\n    return refreshAndRetry(id);\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["upwork","id-mismatch","stale-state","command-execution-error"],"backgroundTag":"id-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}