santifer/career-ops · error · Error
reportNum must be a numeric report number
Error message
reportNum must be a numeric report number
What it means
applicationArtifactPaths builds a stable directory/file bundle key of the form NNN-company-role and requires reportNum to be a string of digits only (checked with /^\d+$/). If reportNum is absent, non-numeric, or contains separators like '#' or leading text, it throws. This guarantees predictable, sortable artifact paths under the output root.
Solutions
- Pass the plain numeric report number: applicationArtifactPaths({ reportNum: 42, company, role }).
- Normalize extracted values first, e.g. String(reportNum).replace(/\D/g, ''), and confirm it is non-empty before calling.
- Check upstream parsing that produced reportNum — log it and fix the extraction from the tracker/report filename.
Example fix
// before
const paths = applicationArtifactPaths({ reportNum: row.num, company, role }); // row.num = '#42'
// after
const num = String(row.num).replace(/^#/, '');
if (!/^\d+$/.test(num)) throw new Error(`bad report number: ${row.num}`);
const paths = applicationArtifactPaths({ reportNum: num, company, role }); Defensive patterns
Strategy: type-guard
Validate before calling
function isNumericReportNum(n) { return /^\d+$/.test(String(n ?? '')); }
if (!isNumericReportNum(reportNum)) throw new Error(`reportNum must be numeric, got ${reportNum}`); Type guard
function isReportNum(v) {
return (typeof v === 'number' && Number.isInteger(v) && v > 0) || (typeof v === 'string' && /^\d+$/.test(v));
} Try / catch
try {
const paths = applicationArtifactPaths({ reportNum, company, role });
} catch (err) {
if (err.message === 'reportNum must be a numeric report number') {
console.error(`Strip decorations from report number: got ${reportNum}`);
return;
}
throw err;
} Prevention
- Extract report numbers with /\d+/ from tracker links or filenames instead of passing raw decorated strings.
- Normalize to a plain integer at ingestion time (parseInt) and store it once.
- Add a small helper stripReportNum() and use it everywhere tracker rows feed artifact paths.
When it happens
Trigger: Calling applicationArtifactPaths with reportNum: undefined, with a string like '042-a' or '#42', with a number in a form String() renders non-numerically (e.g. NaN), or passing a tracker row label instead of the raw report number.
Common situations: Passing values parsed from markdown links like '[042](reports/...)' without stripping the decoration; using tracker row numbers that include prefixes; a variable that failed to populate upstream so undefined flows in.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- apify: invalid actorId
- 4dayweek: invalid URL
- 4dayweek: URL must use HTTPS
- a 40-hex commit --sha is required
- apify: entry has invalid field_map. Each of title, url…
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/91fc7310eb540f98.
Report an issue: GitHub.
Appendix: source
Thrown at application-artifacts.mjs:33
import { isMainModule } from './lib/is-main-module.mjs';
const DEFAULT_OUTPUT_ROOT = resolve('output');
const DECISIONS = new Set(['reuse', 'reuse-with-edits', 'regenerate']);
/** Convert a user-facing label into a safe, readable path segment. */
export function slugifySegment(value, fallback = 'application') {
const slug = String(value ?? '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
return slug || fallback;
}
/** Return all stable paths belonging to one application artifact bundle. */
export function applicationArtifactPaths({ reportNum, company, role, version = 1, root = DEFAULT_OUTPUT_ROOT }) {
if (!/^\d+$/.test(String(reportNum ?? ''))) {
throw new Error('reportNum must be a numeric report number');
}
if (!/^\d+$/.test(String(version ?? '')) || Number(version) < 1) {
throw new Error('version must be a positive integer');
}
const key = `${String(reportNum).padStart(3, '0')}-${slugifySegment(company)}-${slugifySegment(role, 'role')}`;
const applicationRoot = join(resolve(root), key);
const tailoredRoot = join(applicationRoot, 'cv', 'tailored', `v${String(version).padStart(3, '0')}`);
return {
key,
root: applicationRoot,
jd: {
current: join(applicationRoot, 'jd', 'current.md'),
previous: join(applicationRoot, 'jd', 'previous.md'),
},
cv: {
source: {
html: join(applicationRoot, 'cv', 'source', 'original.html'),
pdf: join(applicationRoot, 'cv', 'source', 'original.pdf'),View on GitHub (pinned to aac998c7ed)