santifer/career-ops · error
garena: is not a usable URL segment
Error message
garena: ${name} is not a usable URL segment: ${JSON.stringify(value)} What it means
urlSegment in providers/garena.mjs builds URL path segments by encodeURIComponent-ing configured values (e.g. office names). Before encoding it rejects the values '.' and '..' outright, because percent-encoded dot segments can still be interpreted as relative path traversal by some servers. The error names the field (name) and shows the offending value as JSON.
Solutions
- Set the office field in the portals.yml garena entry to a real office slug (a non-empty name without dot segments).
- Check resolveOffice's fallback: if the office key is missing, provide it explicitly instead of letting a degenerate value flow in.
- Strip or validate config values before they reach the provider (reject '', '.', '..' at config-load time).
- Re-run the scan after fixing the entry to confirm a usable URL is built.
Example fix
// before garena: office: "." # rejected by urlSegment // after garena: office: "singapore"
Defensive patterns
Strategy: validation
Validate before calling
function isSafeSegment(v) {
return typeof v === 'string' && v.length > 0 && v !== '.' && v !== '..';
}
if (!isSafeSegment(entry?.garena?.office)) {
throw new Error(`config: garena.office must be a real slug for "${entry.name}"`);
} Type guard
const isSafeUrlSegment = (v) => typeof v === 'string' && v.length > 0 && v !== '.' && v !== '..';
Try / catch
try {
const jobs = parseGarenaResponse(json, entry);
} catch (e) {
if (e.message.includes('is not a usable URL segment')) {
console.error(`Fix garena.office for "${entry.name}" — got a dot segment`);
return [];
}
throw e;
} Prevention
- Never leave office values empty, '.', or '..' in portals.yml.
- Validate slug-like config fields (^[^./][^/]*$ or similar) when the config is loaded.
- Document which garena fields become URL path segments so editors know the constraints.
When it happens
Trigger: A portals.yml garena.office entry set to '.', '..', or a value that resolves to one of these via resolveOffice (e.g. empty/missing config falling through to a dot, or a stray '.' left in the config by hand).
Common situations: A config cleanup that replaced an office name with '.' meaning 'current directory' by habit; an empty office value coerced into a dot segment upstream; copy-pasting a filesystem-style relative path into the office field.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- landingjobs: invalid URL
- larajobs: invalid URL
- lever: invalid URL
- nodesk: invalid URL
- nofluffjobs: invalid URL
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/4461e94c31d68dc3.
Report an issue: GitHub.
Appendix: source
Thrown at providers/garena.mjs:95
/** @param {{ garena?: { office?: string } }} entry */
function resolveOffice(entry) {
const office = entry && entry.garena && entry.garena.office;
return typeof office === 'string' && office.trim() ? office.trim() : DEFAULT_OFFICE;
}
/**
* Escapes a config-derived value before it is interpolated into a Garena URL.
* `office` is a `portals.yml` segment, so a malformed one is a config bug and
* should fail loudly: separators (`/`, `?`, `#`, ...) are percent-escaped, and
* `.`/`..` are rejected outright because escaping leaves them intact as
* traversal segments.
* @param {string} name - Field name, for the error message.
* @param {string} value
* @returns {string}
*/
function urlSegment(name, value) {
if (value === '.' || value === '..') {
throw new Error(`garena: ${name} is not a usable URL segment: ${JSON.stringify(value)}`);
}
return encodeURIComponent(value);
}
/**
* Escapes the per-posting `id` from the API response. Unlike `urlSegment`, a
* bad value here returns `null` rather than throwing: `id` is host-controlled
* and sits inside the parse loop, so a lone surrogate (URIError) or a `.`/`..`
* traversal segment must drop only that posting, not unwind the whole page
* (#3513).
* @param {string} id
* @returns {string | null}
*/
function idUrlSegment(id) {
if (id === '.' || id === '..') return null;
return safeEncodeURIComponent(id);
}
View on GitHub (pinned to aac998c7ed)