jackwener/OpenCLI · error · ArgumentError

nowcoder detail only accepts canonical https://www.nowcoder.

Error message

nowcoder detail only accepts canonical https://www.nowcoder.com post URLs

What it means

If the input parses as a URL but is not a canonical Nowcoder post link, parseNowcoderPostTarget throws this ArgumentError. The URL must use https, have no credentials, port, or hash, and its host must be nowcoder.com or www.nowcoder.com. This enforces that only unambiguous, official post URLs are accepted so the target can be mapped deterministically to a post id.

Source

Thrown at clis/nowcoder/posts.js:192

    return rows.slice(0, limit);
}

export function parseNowcoderPostTarget(raw) {
    const value = typeof raw === 'string' ? raw.trim() : '';
    if (NUMERIC_ID_PATTERN.test(value)) return { post_type: 'content', value };
    if (UUID_PATTERN.test(value)) return { post_type: 'moment', value: value.toLowerCase() };

    let url;
    try {
        url = new URL(value);
    }
    catch {
        throw new ArgumentError('nowcoder detail requires a numeric content ID, moment UUID, or canonical Nowcoder URL');
    }
    const host = url.hostname.toLowerCase();
    if (url.protocol !== 'https:' || url.username || url.password || url.port || url.hash
        || (host !== 'nowcoder.com' && host !== 'www.nowcoder.com')) {
        throw new ArgumentError('nowcoder detail only accepts canonical https://www.nowcoder.com post URLs');
    }
    const content = url.pathname.match(/^\/discuss\/([1-9]\d*)\/?$/);
    if (content) return { post_type: 'content', value: content[1] };
    const moment = url.pathname.match(/^\/feed\/main\/detail\/([0-9a-f]{32})\/?$/i);
    if (moment) return { post_type: 'moment', value: moment[1].toLowerCase() };
    throw new ArgumentError('Unsupported Nowcoder URL; expected /discuss/<content-id> or /feed/main/detail/<moment-uuid>');
}

export function projectNowcoderDetail(data, target) {
    if (!isRecord(data) || !isRecord(data.frequencyData)) throw new CommandExecutionError('Nowcoder detail returned malformed post data');
    const isContent = target.post_type === 'content';
    const expectedEntityType = isContent ? CONTENT_ENTITY_TYPE : MOMENT_TYPE;
    if (data.entityType !== expectedEntityType) throw new CommandExecutionError('Nowcoder detail returned a mismatched post entity type');
    const uuid = requiredUuid(data.uuid, `${target.post_type} uuid`);
    const entityId = requiredId(data.entityId, `${target.post_type} entity id`);
    const id = isContent ? requiredId(data.id, 'content id') : uuid;
    if (isContent ? id !== target.value : uuid !== target.value) throw new CommandExecutionError('Nowcoder detail returned a different post identity');
    if (!isContent && entityId !== requiredId(data.id, 'moment id')) throw new CommandExecutionError('Nowcoder detail returned mismatched moment entity ids');

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Rewrite the URL to start with https://www.nowcoder.com/.
  2. Strip any #fragment, port, or credentials from the URL.
  3. For mobile links (m.nowcoder.com), reconstruct the canonical /discuss/<id> path manually.
  4. Alternatively bypass URL parsing by passing the bare numeric content ID or moment UUID directly.

Example fix

// before
nowcoder detail http://m.nowcoder.com/discuss/1234567#comments
// after
nowcoder detail https://www.nowcoder.com/discuss/1234567
Defensive patterns

Strategy: validation

Validate before calling

function isCanonicalNowcoderUrl(raw) {
    let url;
    try { url = new URL(typeof raw === 'string' ? raw.trim() : ''); } catch { return false; }
    const host = url.hostname.toLowerCase();
    return url.protocol === 'https:' && !url.username && !url.password && !url.port && !url.hash
        && (host === 'nowcoder.com' || host === 'www.nowcoder.com')
        && (/^\/discuss\/[1-9]\d*\/?$/.test(url.pathname) || /^\/feed\/main\/detail\/[0-9a-f]{32}\/?$/i.test(url.pathname));
}

Type guard

function isCanonicalNowcoderPostUrl(value) { try { const u = new URL(value); return u.protocol === 'https:' && (u.hostname === 'www.nowcoder.com' || u.hostname === 'nowcoder.com') && /^\/discuss\/[1-9]\d*\/?$|^\/feed\/main\/detail\/[0-9a-f]{32}\/?$/i.test(u.pathname); } catch { return false; } }

Try / catch

try {
    target = parseNowcoderPostTarget(raw);
} catch (error) {
    if (error instanceof ArgumentError && String(error.message).includes('canonical')) {
        console.error('Rewrite the link as https://www.nowcoder.com/discuss/<id> or /feed/main/detail/<uuid>, with no port, credentials, or #fragment');
    }
    throw error;
}

Prevention

When it happens

Trigger: A URL-parseable input that fails the canonicality check: http:// instead of https://, embedded username/password or a port, a #fragment, or a host other than nowcoder.com/www.nowcoder.com (e.g., m.nowcoder.com, a mirror, or a different site entirely).

Common situations: Users copying http:// links from old bookmarks; mobile-host links (m.nowcoder.com); URLs with tracking fragments (#comment-1) or query-ordered ports; pasting links from other sites into the nowcoder detail command.

Related errors


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