{"record":{"id":"ff4bb53b498ccbe5","repo":"jackwener/OpenCLI","slug":"not-a-git-repository","errorCode":null,"errorMessage":"Not a git repository","messagePattern":"Not a git repository","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"autoresearch/engine.ts","lineNumber":90,"sourceCode":"  private bestMetric: number = 0;\n  private currentMetric: number = 0;\n  private iteration: number = 0;\n\n  constructor(config: AutoResearchConfig, logPath: string, callbacks: EngineCallbacks) {\n    this.config = config;\n    this.logger = new Logger(logPath);\n    this.callbacks = callbacks;\n  }\n\n  private log(msg: string): void {\n    this.callbacks.onStatus?.(msg);\n  }\n\n  /** Phase 0: Precondition checks */\n  private checkPreconditions(): void {\n    // Git repo exists\n    try { execStrict('git rev-parse --git-dir'); }\n    catch { throw new Error('Not a git repository'); }\n\n    // Clean working tree\n    const status = exec('git status --porcelain');\n    if (status) throw new Error(`Working tree not clean:\\n${status}`);\n\n    // No stale locks\n    if (existsSync(join(ROOT, '.git', 'index.lock'))) {\n      throw new Error('Stale .git/index.lock found — remove it first');\n    }\n\n    // Not detached HEAD\n    try { execStrict('git symbolic-ref HEAD'); }\n    catch { throw new Error('Detached HEAD — checkout a branch first'); }\n  }\n\n  /** Phase 5: Run verify command and extract metric */\n  private runVerify(): number | null {\n    this.log('  verify...');","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/autoresearch/engine.ts#L72-L108","documentation":"This MALFORMED_RESULT error is produced by the huodongxing CLI's extractEventRowsPayload when the scraper did find event card elements in the returned page, but none of them could be converted into stable event rows. Extraction requires each card to yield an event link matching /event/<id> plus a non-empty title; when the markup no longer matches that contract, the library reports the payload as malformed rather than returning empty or partial data. It signals a site-side markup change (or bot-wall interstitial rendering card-like nodes), not a caller mistake.","triggerScenarios":"Calling extractEventRows (which delegates to extractEventRowsPayload) when the DOM query returns cards.length > 0 but zero cards match the '/event/<id>' link + non-empty title pattern, e.g. after Huodongxing renames routes, wraps links, or lazy-renders titles.","commonSituations":"Huodongxing ships a front-end redesign or A/B markup change; a CDN/anti-bot edge page injects card-like HTML without real event links; titles are rendered via images or client-side JS that hasn't executed in the scraping session.","solutions":["Wait and retry the request from the same browser session — confirm the page actually shows real event cards (not a busy/interstitial page) via a screenshot or DOM dump.","Inspect the current card markup and update the extraction selectors/logic to match the new '/event/<id>' link shape and title element.","Dump the failing HTML to verify whether titles are empty because JS hasn't run; if so, wait for hydration (e.g. waitForSelector on the title) before extracting.","If the site changed its URL scheme permanently, update the expected pattern (regex) for event links inside extractEventRowsPayload."],"exampleFix":"// before\nconst links = card.querySelectorAll('a[href^=\"/event/\"]');\nif (!links.length) throw malformed;\n// after\n// accept new absolute/relative event link shapes and wait for titles\nawait page.waitForSelector('[data-event-title]:not(:empty)');\nconst links = card.querySelectorAll('a[href*=\"/event/\"]');","handlingStrategy":"validation","validationCode":"const cards = document.querySelectorAll('.event-card');\nif (cards.length > 0 && !Array.from(cards).some(c => c.querySelector('a[href*=\"/event/\"]')?.textContent?.trim())) {\n  console.warn('Card markup drift detected: no /event/<id> links with titles — abort before calling extractEventRows');\n}","typeGuard":"function isStableEventRow(row: unknown): row is { id: string; title: string } {\n  return typeof row === 'object' && row !== null &&\n    typeof (row as any).id === 'string' && /^.+/.test((row as any).id) &&\n    typeof (row as any).title === 'string' && (row as any).title.trim().length > 0;\n}","tryCatchPattern":"try {\n  const rows = await extractEventRows();\n} catch (err) {\n  if (err.code === 'MALFORMED_RESULT') {\n    log.warn('Huodongxing markup changed:', err.hint);\n    // fall back to raw HTML dump for manual selector update\n  } else throw err;\n}","preventionTips":["Add a screenshot/DOM snapshot of the events page to your scraping debug artifacts each run so markup drift is visible.","Write a smoke test against a saved HTML fixture of the events page that asserts rows still extract.","Keep the '/event/<id>' link pattern centralized so a route change is a one-line fix.","Wait for client-side hydration (title elements non-empty) before extracting."],"tags":["scraping","dom-parsing","selector-drift","cli"],"backgroundTag":"dom-structure-changed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}