{"record":{"id":"83dd81fd2bd77be7","repo":"jackwener/OpenCLI","slug":"upwork-tab-must-be-one-of-object-keys-feed-tabs","errorCode":null,"errorMessage":"upwork tab must be one of ${Object.keys(FEED_TABS).join(' / ')}, got \"${value}\"","messagePattern":"upwork tab must be one of (.+?), got \"(.+?)\"","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/upwork/utils.js","lineNumber":91,"sourceCode":" * Upwork job ids are the ciphertext form starting with `~01` or `~02`\n * (the encoded uid surfaced everywhere in URLs and search results).\n * Accepts a bare ciphertext or a full `/jobs/~02…` URL.\n */\nexport function requireCiphertext(value) {\n    let id = String(value ?? '').trim();\n    if (!id) throw new ArgumentError('upwork job id is required');\n    const urlMatch = id.match(/~0[12]\\d+/);\n    if (urlMatch) id = urlMatch[0];\n    if (!CIPHERTEXT_PATTERN.test(id)) {\n        throw new ArgumentError(`upwork job id \"${value}\" is not a valid ciphertext (expected ~01… or ~02… followed by digits)`);\n    }\n    return id;\n}\n\nexport function requireFeedTab(value, defaultValue = 'best-matches') {\n    const v = String(value ?? defaultValue).trim().toLowerCase();\n    if (!FEED_TABS[v]) {\n        throw new ArgumentError(`upwork tab must be one of ${Object.keys(FEED_TABS).join(' / ')}, got \"${value}\"`);\n    }\n    return v;\n}\n\nexport function requireSort(value, defaultValue = 'recency') {\n    const v = String(value ?? defaultValue).trim().toLowerCase();\n    if (!SORT_VALUES.has(v)) {\n        throw new ArgumentError(`upwork sort must be one of ${Array.from(SORT_VALUES).join(' / ')}, got \"${value}\"`);\n    }\n    return v;\n}\n\n/**\n * Build the Upwork search URL. Only forwards filters the user actually\n * supplied so the URL stays canonical and round-trippable.\n */\nexport function buildSearchUrl({ query, location, category, sort, page, perPage }) {\n    const params = new URLSearchParams();","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/upwork/utils.js#L73-L109","documentation":"requireFeedTab validates the feed tab option against the FEED_TABS map, which contains only 'best-matches' and 'most-recent'. Unknown values (after trim/lowercase) throw ArgumentError listing the valid keys.","triggerScenarios":"Calling the feed command with --tab set to anything other than best-matches/most-recent, e.g. 'saved-jobs', 'Best Matches' with a space, or a typo like 'bestmatch'.","commonSituations":"Guessing tab names instead of checking help output; copying tab labels from the Upwork web UI which differ from the CLI keys; casing/space variants that the trim().toLowerCase() normalization doesn't fix (e.g. internal spaces).","solutions":["Use exactly 'best-matches' or 'most-recent' (hyphenated, lowercase)","Omit --tab to use the default 'best-matches'","Run the command's --help to see the enumerated tab values"],"exampleFix":"// before\n$ opencli upwork feed --tab \"Best Matches\"\n// after\n$ opencli upwork feed --tab best-matches","handlingStrategy":"validation","validationCode":"const FEED_TABS = ['best-matches', 'most-recent'];\nfunction isValidTab(t) {\n  return FEED_TABS.includes(String(t ?? '').trim().toLowerCase());\n}","typeGuard":"function isFeedTab(v) {\n  return v === 'best-matches' || v === 'most-recent';\n}","tryCatchPattern":"try {\n  await feed({ tab });\n} catch (e) {\n  if (e instanceof ArgumentError && /tab must be one of/.test(e.message)) {\n    return feed({ tab: 'best-matches' });\n  }\n  throw e;\n}","preventionTips":["Use exact hyphenated lowercase keys from --help","Don't copy human-readable tab labels from the web UI","Omit --tab to accept the default"],"tags":["validation","cli","enum","upwork"],"backgroundTag":"invalid-enum-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}