{"record":{"id":"22c716ba96159e5a","repo":"jackwener/OpenCLI","slug":"book-rank-must-be-books-length","errorCode":null,"errorMessage":"book-rank must be <= ${books.length}","messagePattern":"book-rank must be <= (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/weread/book-search.js","lineNumber":228,"sourceCode":"            return exact[0].readerUrl;\n    }\n    const sameTitle = htmlEntries.filter((entry) => normalizeSearchText(entry.title) === title);\n    return sameTitle.length === 1 ? sameTitle[0].readerUrl : '';\n}\n\nasync function searchBookByQuery(bookQuery, bookRank) {\n    const url = new URL('/web/search/global', `${WEREAD_WEB_ORIGIN}/web`);\n    url.searchParams.set('keyword', bookQuery);\n    const data = await fetchJson(url, 'WeRead book search');\n    if (!Array.isArray(data?.books)) {\n        throw new CommandExecutionError('WeRead book search returned malformed books');\n    }\n    const books = data.books;\n    if (books.length === 0) {\n        throw new EmptyResultError('weread book-search', `No WeRead books found for \"${bookQuery}\"`);\n    }\n    if (bookRank > books.length) {\n        throw new ArgumentError(`book-rank must be <= ${books.length}`, `Only ${books.length} book search result(s) were returned for \"${bookQuery}\"`);\n    }\n    const bookInfo = books[bookRank - 1]?.bookInfo ?? {};\n    const selected = {\n        bookId: normalizeSearchText(bookInfo.bookId),\n        title: normalizeSearchText(bookInfo.title),\n        author: normalizeSearchText(bookInfo.author),\n        readerUrl: '',\n        chapters: [],\n    };\n    if (!selected.bookId) {\n        throw new CommandExecutionError(`WeRead book search result ${bookRank} is missing bookId`);\n    }\n    const htmlEntries = await loadSearchHtmlEntries(bookQuery);\n    selected.readerUrl = resolveReaderUrlForBook(selected, htmlEntries);\n    const readerMetadata = await loadReaderMetadata(selected.readerUrl);\n    return {\n        ...selected,\n        ...Object.fromEntries(Object.entries(readerMetadata ?? {}).filter(([, value]) => value != null && value !== '' && !(Array.isArray(value) && value.length === 0))),","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/weread/book-search.js#L210-L246","documentation":"searchBookByQuery throws this ArgumentError when the --book-rank supplied by the caller exceeds the number of results the WeRead book search API returned. It is a guard before indexing books[bookRank - 1] so the CLI never silently selects an undefined entry. The message includes the valid upper bound so the caller can pick a rank in range.","triggerScenarios":"Calling resolveBookTarget/book with a numeric rank greater than data.books.length after a successful search — e.g. book-rank 5 when the query 'x' matched only 2 books.","commonSituations":"Developers assume a popular-sounding query returns many hits; WeRead's search returns few or zero relevant results, so a hardcoded rank overflows. Also happens when the query is misspelled or in the wrong language, shrinking the result list.","solutions":["Lower book-rank to a value between 1 and the count reported in the error message.","Refine the search query (exact title, author, or Chinese text) so more/better results are returned and the desired book is within range.","Pass the book URL or raw bookId instead of a rank to select the book deterministically."],"exampleFix":"// before\ncli book search '深度学习' --rank 10\n// after (only 3 results returned)\ncli book search '深度学习' --rank 3","handlingStrategy":"validation","validationCode":"// no local count is known before the call, but clamp interactively:\nconst rank = Number(opts['book-rank']);\nif (!Number.isInteger(rank) || rank < 1 || rank > 20) {\n  throw new Error('book-rank must be a small positive integer; run the search first to see the count');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const book = await book(target, rank);\n} catch (e) {\n  if (/book-rank must be <= /.test(e.message)) {\n    const max = Number(e.message.match(/<= (\\d+)/)?.[1] ?? 0);\n    return book(target, Math.min(rank, max));\n  }\n  throw e;\n}","preventionTips":["Run the search listing first and choose a rank from the printed results.","Never hardcode book-rank; derive it from a previous search step.","Prefer passing a book URL or bookId over a rank."],"tags":["argument-validation","search","weread"],"backgroundTag":"index-out-of-range","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}