jackwener/OpenCLI · error · CommandExecutionError

Upwork job-detail store had an unexpected job shape; expecte

Error message

Upwork job-detail store had an unexpected job shape; expected an object.

What it means

The `upwork detail` command reads the job posting from the Vuex store (window.$nuxt.$store.state.jobDetails.job) after the browser-bridge payload reports ready and a job exists. This CommandExecutionError is thrown when that job value is truthy but is not a plain object (e.g. a string, number, or array), meaning Upwork's rehydrated store no longer matches the expected schema. It guards against downstream property access (job.ciphertext, job.title, ...) failing or producing garbage.

Source

Thrown at clis/upwork/detail.js:95

        }
        catch (e) {
            throw new CommandExecutionError(`Failed to read Upwork job-detail store: ${e?.message ?? e}`, 'The Vuex store was not reachable; try again after opening Upwork in the connected browser.');
        }

        if (payload?.onLogin) {
            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 [{

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the command — a transient hydration glitch can produce a malformed store value; re-running often repopulates it correctly.
  2. Open the job URL (https://www.upwork.com/jobs/<id>) in the connected browser and confirm the page renders normally; if not, wait for Upwork to fix or use the web UI.
  3. Check for an Upwork A/B or redesign (inspect window.$nuxt.$store.state.jobDetails in DevTools) and update the CLI to a version matching the new store schema.
  4. If the job genuinely fails to load, fall back to `upwork search` / `upwork feed` list data instead of detail.

Example fix

// before
const job = payload.job; // assumed plain object
// after
const job = payload.job && typeof payload.job === 'object' && !Array.isArray(payload.job)
    ? payload.job
    : (() => { throw new CommandExecutionError('unexpected job shape'); })();
Defensive patterns

Strategy: type-guard

Type guard

function isPlainObject(v) {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}
// after the call: if (!isPlainObject(job)) fall back or retry

Try / catch

try {
  const detail = await upwork.detail(id);
} catch (e) {
  if (/unexpected job shape/.test(e.message)) {
    // retry once, then degrade to list-row data
    return retryOrFallback(id);
  }
  throw e;
}

Prevention

When it happens

Trigger: Running `upwork detail <id>` when payload.job is truthy but isPlainObject(payload.job) returns false — e.g. Upwork changes jobDetails.job to a wrapped/serialized value (a string, an array of versions, or a null-like sentinel that is still truthy) after a site schema change, or the browser bridge mangles the JSON.parse(JSON.stringify(s.job)) round-trip.

Common situations: Upwork shipping a frontend redesign that changes the Vuex jobDetails shape; running a stale version of this CLI against a newer Upwork build; SSR/hydration differences that put a raw string or non-object blob into store.state.jobDetails.job.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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