Skip to content

[failproofai-570] fix: validate translated docs pages at generation time and retry on failure#570

Merged
NiveditJain merged 2 commits into
mainfrom
luv-570
Jul 17, 2026
Merged

[failproofai-570] fix: validate translated docs pages at generation time and retry on failure#570
NiveditJain merged 2 commits into
mainfrom
luv-570

Conversation

@NiveditJain

@NiveditJain NiveditJain commented Jul 17, 2026

Copy link
Copy Markdown
Member

Problem

The daily Translate Docs workflow kept failing — 5 of the last 15 runs — with errors like:

consolidate | Validate generated docs config |
  docs/de/agenteye/cli-skill.mdx: There is a syntax error in your frontmatter on line 2
error Failed to parse MDX files, please correct errors before continuing

Two compounding problems:

  1. Validation was at the wrong end of the pipeline. mintlify validate runs in the consolidate job — after all 14 per-language matrix jobs have finished and spent their tokens. One bad page in one language fails the whole batch and nothing publishes (consolidate is gated on needs.translate.result == 'success').
  2. The repo's own validate:mdx net was blind to this class. findMdxParseError calls stripFrontmatter() and compiles only the body, so a frontmatter YAML error was structurally invisible to it — only the last-step mintlify validate ever saw it.

Root cause of the recurring instance: docs/agenteye/cli-skill.mdx has description: "Ask your coding agent \"is anything broken today?\" …" (escaped inner quotes). The translator re-emits those inner quotes unescaped into the target-language scalar, producing invalid YAML on the title:/description: line.

Fix — validate each generated page, retry with the error fed back

Per the ask: "instead of checking .mdx validity at the last step, check each generated .mdx and, if invalid, re-translate with the validation error; cap the tries."

  • validate-mdx.ts gains findFrontmatterError (YAML parse via the already-present yaml dep, file-relative line numbers) and findPageError (frontmatter then body). validate:mdx now runs findPageError, making the CI net a strict superset of mintlify validate — it verified clean on all 647 existing pages.
  • validate-translation.ts (new) — findTranslationError validates the exact bytes each translator will write (sanitizers + link-rewrite for MDX pages; disclaimer + RTL <div> wrapper for the README): frontmatter YAML, frontmatter key-parity against the English source (catches a dropped or renamed block, which is still valid YAML and which mintlify tolerates), and MDX body.
  • translator.tstranslateValidated re-translates with the validation error appended to the prompt until the page passes or TRANSLATE_MAX_ATTEMPTS (integer ≥ 1, default 3; distinct from the SDK-transport TRANSLATE_MAX_RETRIES). On exhaustion it throws before the write, so a broken page is never written to disk and never cached — the cache keys only the English source hash, so a cached-invalid page could otherwise never self-heal.
  • Prevention — system-prompt rule 3 now carries the same frontmatter-quoting guidance rule 2 has always given for JSX attributes, cutting the failure off at the source.
  • Retries do not add proxy load — a retry re-translates inside the worker's existing concurrency slot, so peak concurrency stays 16 regardless of attempts (workflow comment corrected from a stale 4×2=8).

Verification

  • End-to-end: reproduced the exact CI failure; findPageError now catches it at line 3, and findTranslationError produces caret-underlined, model-actionable repair feedback. No false positive on the valid English source.
  • 31 new unit tests (frontmatter validation, the translation gate, the retry loop incl. exhaustion / no-write / no-cache / token-summing / max_tokens-not-retried).
  • Full suite green: 2232 tests across 126 files, tsc clean, lint clean (0 errors), validate:mdx clean on all 647 pages.

🤖 Generated with Claude Code

https://claude.ai/code/session_019s6wvcZ1NZHmJBfDbzyo65

Summary by CodeRabbit

  • New Features
    • Translated pages are now validated for both frontmatter YAML and MDX body errors before being saved.
    • Automatic retry now uses validation feedback, with a retry count surfaced in run summaries.
    • Translation attempts that fail validation are no longer written or cached.
  • Bug Fixes
    • Improved validation and error reporting, including better handling of malformed or missing frontmatter.
    • Updated rules to prevent specific frontmatter-quoting failures from breaking translation jobs.
  • Tests
    • Expanded test coverage for validation and retry behavior.
  • Documentation
    • Updated the changelog with the translation pipeline improvements.

…on failure

The daily translate-docs run kept failing (5 of the last 15 runs) when
`mintlify validate` in the consolidate job rejected a translated page's
frontmatter YAML — the model re-emitting an escaped inner quote from a
`description:` value as an unescaped `"`, e.g.
  de/agenteye/cli-skill.mdx: syntax error in your frontmatter on line 2
That check runs only after all 14 language jobs have spent their tokens,
and one bad page in one language blocked the whole batch from publishing.

The repo's own validate:mdx net could not catch this class at all:
findMdxParseError blanks the frontmatter before compiling, so a
frontmatter YAML error was structurally invisible to it.

- validate-mdx.ts: add findFrontmatterError (YAML parse via the existing
  `yaml` dep, file-relative line numbers) and findPageError (frontmatter
  then body); validate:mdx now runs the latter, so the CI net is a strict
  superset of `mintlify validate` across all 647 pages.
- validate-translation.ts (new): findTranslationError validates the exact
  bytes each translator will write — frontmatter YAML, key-parity against
  the English source (catches a dropped/renamed block, which is still
  valid YAML), and MDX body.
- translator.ts: translateValidated re-translates with the validation
  error fed back until the page passes or TRANSLATE_MAX_ATTEMPTS (default
  3, distinct from the SDK-level TRANSLATE_MAX_RETRIES). On exhaustion it
  throws before the write, so a broken page is never written or cached.
- System-prompt rule 3 now carries the frontmatter-quoting guidance rule
  2 has always given for JSX attributes, cutting the failure off at source.

Adds 31 unit tests; validate:mdx passes on all 647 existing pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019s6wvcZ1NZHmJBfDbzyo65
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1dc361ef-efd4-4d18-ad6a-ccdb8abcaf5e

📥 Commits

Reviewing files that changed from the base of the PR and between 6000368 and e7e6ca5.

📒 Files selected for processing (2)
  • __tests__/scripts/translate-docs/validate-translation.test.ts
  • scripts/translate-docs/validate-translation.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/scripts/translate-docs/validate-translation.test.ts
  • scripts/translate-docs/validate-translation.ts

📝 Walkthrough

Walkthrough

Translation now validates rendered MDX and README bytes, retries invalid generations with feedback up to a configured limit, prevents invalid output from being written or cached, reports retry counts, and extends MDX validation to frontmatter YAML errors.

Changes

Translation validation and retry pipeline

Layer / File(s) Summary
Page and frontmatter validation
scripts/validate-mdx.ts, scripts/translate-docs/validate-translation.ts, __tests__/scripts/validate-mdx.test.ts, __tests__/scripts/translate-docs/validate-translation.test.ts
Frontmatter YAML, key parity, and MDX body validation are combined into translation-time and deploy-time checks with actionable errors and excerpts.
Bounded retry orchestration
scripts/translate-docs/translator.ts, scripts/translate-docs/types.ts, __tests__/scripts/translate-docs/translator.test.ts
Translations retry validation failures with feedback up to TRANSLATE_MAX_ATTEMPTS, aggregate usage, and return attempt metadata.
Validated translator integrations
scripts/translate-docs/mdx-translator.ts, scripts/translate-docs/readme-translator.ts, scripts/translate-docs/cli.ts, __tests__/scripts/translate-docs/mdx-translator.test.ts
MDX and README translators validate final rendered bytes before writing, while the CLI reports retried translations.
Workflow and release support
.github/workflows/translate-docs.yml, CHANGELOG.md
Concurrency documentation and release notes describe the validation and retry behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Translator
  participant Validator
  participant Filesystem
  CLI->>Translator: translate page or README
  Translator->>Validator: validate rendered bytes
  Validator-->>Translator: return success or validation error
  Translator->>Translator: retry with feedback when invalid
  Translator->>Filesystem: write validated output
  Translator-->>CLI: return attempt count
Loading

Possibly related PRs

Suggested reviewers: hermes-exosphere

Poem

I’m a rabbit with pages to mend,
Checking each carrot from start to end.
If YAML goes wrong, I hop back twice,
Then send clean MDX, neat as ice.
Valid bytes thump through the night.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required template and omits the Type of Change and Checklist sections. Add the template sections: Description, Type of Change, and Checklist, with the required checkboxes for lint, tsc, tests, and build.
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: validating translated docs at generation time and retrying on failure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Automated code review started - full review. Results will be posted here.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jul 17, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@__tests__/scripts/translate-docs/validate-translation.test.ts`:
- Around line 46-70: The translation validation tests currently allow extra
frontmatter keys and do not cover malformed frontmatter on frontmatter-less
sources. Update the extra-key test to expect a validation error, and add a test
using a source without frontmatter plus malformed leading frontmatter that
asserts rejection rather than a blank-body success. Anchor the changes around
findTranslationError and the existing extra-key/body-only test cases.

In `@scripts/translate-docs/validate-translation.ts`:
- Around line 82-112: The validator around sourceKeys and frontmatterKeys must
validate rendered frontmatter before branching on the source shape, so malformed
or added frontmatter is rejected even when the source has no frontmatter.
Require exact key-set parity by rejecting both missing and extra keys, and
update the tests in
__tests__/scripts/translate-docs/validate-translation.test.ts to reject extra
keys and malformed added frontmatter for a frontmatter-less source.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea2f8362-fe20-4798-85a9-3cca68f59a82

📥 Commits

Reviewing files that changed from the base of the PR and between c1189ca and 6000368.

📒 Files selected for processing (13)
  • .github/workflows/translate-docs.yml
  • CHANGELOG.md
  • __tests__/scripts/translate-docs/mdx-translator.test.ts
  • __tests__/scripts/translate-docs/translator.test.ts
  • __tests__/scripts/translate-docs/validate-translation.test.ts
  • __tests__/scripts/validate-mdx.test.ts
  • scripts/translate-docs/cli.ts
  • scripts/translate-docs/mdx-translator.ts
  • scripts/translate-docs/readme-translator.ts
  • scripts/translate-docs/translator.ts
  • scripts/translate-docs/types.ts
  • scripts/translate-docs/validate-translation.ts
  • scripts/validate-mdx.ts

Comment thread __tests__/scripts/translate-docs/validate-translation.test.ts
Comment thread scripts/translate-docs/validate-translation.ts
@hermes-exosphere

Copy link
Copy Markdown
Contributor

Phase 1-2 complete. Fetched all context — 13 files changed (focused translation validation changes). Now building and running tests for real evidence.

CodeRabbit flagged that findTranslationError only validated frontmatter YAML
when the English source had a frontmatter block. For a frontmatter-less source
(the READMEs and ~15 .mdx pages), a malformed block the model added would be
blanked by findMdxParseError before compiling and sail through — reopening, for
that source shape, the exact blind spot this module closes. Hoist
findFrontmatterError(rendered) ahead of the source-shape branch so the rendered
frontmatter is validated unconditionally.

The key-parity check stays a subset (extra keys tolerated): Mintlify ignores
unknown frontmatter keys, so rejecting an extra key cannot prevent a deploy
failure — it can only burn a retry and risk exhausting the batch on a page that
would publish fine. Reject content loss (missing/renamed keys); allow harmless
additions.

Adds 2 tests: malformed added frontmatter on a frontmatter-less source is
rejected; well-formed added frontmatter is allowed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019s6wvcZ1NZHmJBfDbzyo65
@NiveditJain

Copy link
Copy Markdown
Member Author

Thanks @coderabbitai — one point taken, one respectfully held.

Frontmatter-less source + malformed added frontmatter — fixed (commit e7e6ca5). You're right: findTranslationError only validated frontmatter YAML when the source had a block, so for a frontmatter-less source (the READMEs and ~15 .mdx pages) a malformed --- block the model added would be blanked by findMdxParseError before compiling and slip through — reopening, for that source shape, the exact blind spot this module exists to close. findFrontmatterError(rendered) now runs unconditionally, ahead of the source-shape branch. Added two tests (malformed added frontmatter → rejected; well-formed added frontmatter → allowed).

Exact key-parity (rejecting extra keys) — deliberately kept as a subset check. Mintlify ignores unknown frontmatter keys, so an extra key cannot cause a deploy failure. Rejecting it could only burn a retry and risk exhausting the attempt budget on a page that would publish fine — which is precisely the "one page blocks all 14 languages" failure mode this PR exists to eliminate. The gate rejects content loss (missing/renamed keys, a real regression) and tolerates harmless additions. Making it stricter than the deploy here would trade the reliability we're buying for fidelity the deploy doesn't require. Expanded the code comment to make this explicit.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@NiveditJain Thanks for the thorough follow-up. The unconditional findFrontmatterError(rendered) check closes the frontmatter-less-source gap; the new positive and negative tests cover the relevant behavior well.

Your rationale for subset key parity is sound: preserving required source keys prevents semantic loss, while allowing deploy-ignored additions avoids spending the bounded retry budget on non-blocking differences. I’m happy to leave that policy as implemented.

🐇 ✅


✏️ Learnings added
Learnt from: NiveditJain
URL: https://github.com/FailproofAI/failproofai/pull/570

Timestamp: 2026-07-17T15:39:44.185Z
Learning: In `scripts/translate-docs/validate-translation.ts`, translated frontmatter validation intentionally requires all English-source frontmatter keys to remain but permits extra translated keys. Mintlify ignores unknown keys, so rejecting additions would consume bounded translation retries without preventing a deploy failure; missing or renamed source keys remain validation failures.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Build & test complete (CI-verified). Results:

  • Build: pass (1m3s)
  • Tests: 2232 passed (default config + hook-log-file + log-debug variants)
  • Docs validation: ✓ 647 MDX pages parsed cleanly (with new findPageError — frontmatter YAML + MDX body)
  • Lint/quality: pass (0 errors)
  • E2E tests: pass
  • TypeScript: clean

Full review incoming...

@hermes-exosphere

Copy link
Copy Markdown
Contributor

🔍 Automated Code Review — PR #570

📋 Executive Summary

This PR shifts validation of translated docs pages from the final consolidate CI job (after all 14 languages are done) to generation time with automatic retry. When a page fails validation, the error is fed back to the model for a re-translate — capped at 3 attempts. The validate:mdx CI net now also catches frontmatter YAML errors (was previously blind to them, only checking MDX body). The architecture is well-designed: the retry loop lives in translateValidated() which validates the rendered bytes (post-sanitize + post-wrappers) — so a pass here truly equals a deploy pass.


📊 Change Architecture

graph TD
    A["translateContent()"] -->|"+feedback param"| B["translateValidated() [NEW]"]
    B -->|"render(raw)"| C["render callback"]
    C --> D["findTranslationError() [NEW]"]
    D -->|"valid"| E["writeFileSync + cache"]
    D -->|"invalid"| B
    D -->|"exhausted"| F["THROW"]
    D --> G["findFrontmatterError() [NEW]"]
    D --> H["frontmatterKeys() [NEW]"]
    D --> I["findMdxParseError() [existing]"]
    G -.-> J["findPageError() [NEW entry]"]
    I -.-> J
    J --> K["validate:mdx CI"]
    style B fill:#90EE90
    style D fill:#90EE90
    style G fill:#90EE90
    style H fill:#90EE90
    style J fill:#FFD700
Loading

🔴 Breaking Changes

No breaking changes detected. All new exports are additive. Existing function signatures extended with optional params. No DB, API, or config changes.


⚠️ Issues Found

  1. 🟡 Design Tensionvalidate-translation.ts:105 — Frontmatter key parity uses subset check, not exact match. Extra keys in translation are tolerated because they can't break the deploy. This is intentional and documented. No action required.

  2. 🟡 Edge Casevalidate-translation.ts:82-94 — When English source has no frontmatter (README), malformed YAML in translation would pass. The prompt forbids adding frontmatter and the README wrapper doesn't use one. No action required.

  3. 🔵 Infovalidate-translation.ts:36FRONTMATTER_RE duplicated from validate-mdx.ts for module independence. Documented as intentional. No action required.


🔬 Logical / Bug Analysis

Retry loop correctness — Empty responses caught, transport errors not retried, token summing accurate, no partial writes or cache poisoning on exhaustion.

Frontmatter validation — File-relative line numbers consistent with existing convention. findPageError short-circuits correctly (frontmatter first, then body).

Translation validation — Correct ordering: YAML validity, then key parity, then MDX body. Unparseable YAML surfaces as syntax error, not "missing keys."

System prompt — Rule 3 now warns about unescaped quotes in YAML values, mirroring existing JSX attribute guidance. Directly addresses root cause.


🧪 Evidence — Build & Test Results

CI Run #29592217991 — ALL GREEN

Job Result Duration
build pass 1m3s
test (default) 2232 passed 1m18s
test (hook-log-file) pass 1m12s
test (log-debug) pass 1m11s
test-e2e pass 51s
docs (validate:mdx) 647 pages clean 1m36s
quality (lint+tsc) pass 1m9s

🔗 Issue Linkage

⚠️ No issue linked. Consider creating one for traceability.


👥 Human Review Feedback

  • @coderabbitai[bot]: Two auto-comments about frontmatter strictness. Both are design choices, not bugs. ℹ️ Noted.
  • No human review comments on this PR.

💡 Suggestions

  1. Consider exporting FRONTMATTER_RE from validate-mdx.ts to eliminate the intentional duplication.
  2. Consider docsDir configurability at CLI level rather than per-function (test-only plumbing).
  3. Snapshot test for buildRetryFeedback format to catch accidental prompt drift.

🏆 Verdict

VERDICT: APPROVED

High-quality, well-architected fix. Retry loop correctly bounded, transport errors propagate properly, cache/write safety solid. 31+ new tests covering all paths. Full CI green: 2232 tests, 647 pages clean, lint zero, tsc clean, e2e green.


Automated code review · 2026-07-17 ~18:05 UTC

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: Approved. ✅

Full analysis at the summary comment. No blocking issues found — well-architected fix with comprehensive test coverage. CI all green: 2232 tests, 647 pages validate clean, lint zero.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@hermes-exosphere Thanks for the thorough, evidence-backed review. The generation-time gate, bounded retry behavior, and validation of rendered output are all well covered, and the green CI results support the approval verdict.

One minor note for the record: the frontmatter-less-source malformed-frontmatter edge case listed in the report was subsequently fixed in e7e6ca5, with coverage for both malformed and valid added frontmatter.

🐇 ✅

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: Approved. ✅

@coderabbitai coderabbitai Bot removed the enhancement New feature or request label Jul 17, 2026
@hermes-exosphere

Copy link
Copy Markdown
Contributor

Automated code review started - full review. Results will be posted here.

@hermes-exosphere

Copy link
Copy Markdown
Contributor

🔍 Automated Code Review — PR #570 (Re-review)

📋 Executive Summary

This PR shifts docs translation validation from the final consolidate CI job to generation time with automatic retry. Every translated page is validated (frontmatter YAML + key parity + MDX body) against the exact bytes that will be written, and invalid output triggers a re-translate capped at 3 attempts. Two commits: the initial implementation (6000368) and a fix (e7e6ca5) that validates frontmatter for every source shape — even frontmatter-less sources — closing a blind spot identified in the prior review. Code is well-architected, the safety invariants are sound, and tests are comprehensive. No blocking issues found.


📊 Change Architecture

graph TD
    A["translateContent<br/>(raw LLM call)"] -->|"+ feedback param"| B["translateValidated<br/>(NEW: retry loop)"]
    B -->|render| C["render callback<br/>(sanitize + link-rewrite)"]
    C -->|validate| D["findTranslationError<br/>(NEW)"]
    D --> E["findFrontmatterError<br/>(NEW in validate-mdx)"]
    D --> F["frontmatterKeys parity<br/>(subset check)"]
    D --> G["findMdxParseError<br/>(existing)"]
    E -->|"YAML parse"| H["yaml@2.9.0<br/>(already a dep)"]
    B -->|"fail: throw before write"| I["No file written<br/>No cache entry"]
    B -->|"pass"| J["writeFileSync<br/>update cache"]
    K["translateMdxPage"] --> B
    L["translateReadme"] --> B
    style B fill:#90EE90
    style D fill:#90EE90
    style E fill:#90EE90
    style F fill:#87CEEB
    style G fill:#87CEEB
Loading

Legend: 🟢 New | 🔵 Modified | 🟡 Breaking Change Risk


🔴 Breaking Changes

✅ No breaking changes detected. All existing public APIs (translateMdxPage, translateReadme, TranslationResult) are backward-compatible. The new attempts field on TranslationResult is optional. The translateContent function gained an optional 5th parameter (feedback) that no existing callers pass. The CLI summary now prints retry counts but this is additive, not a format change.


⚠️ Issues Found

  1. 🔵 Info (non-blocking)scripts/translate-docs/validate-translation.ts:33 — The FRONTMATTER_RE regex is duplicated from scripts/validate-mdx.ts:49. The comment explains this is intentional (to avoid depending on an unexported internal constant), which is a reasonable design choice for a 13-character regex, but worth noting.

  2. 🔵 Info (non-blocking)scripts/translate-docs/translator.ts:251 — Error message uses attempt(s) instead of a proper plural: "after ${MAX_ATTEMPTS} attempt(s)". Should be "after ${MAX_ATTEMPTS} attempt${MAX_ATTEMPTS === 1 ? '' : 's'}" or simply "after ${MAX_ATTEMPTS} attempts" (since 1 would mean no retries needed, the exhaustion path only triggers with ≥ 1 failed attempt).


🔬 Logical / Bug Analysis

Retry loop correctness (translateValidated): The for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) loop properly bounds retries. Each iteration: calls translateContent, sums token usage, checks for empty output (re-treated as validity failure), renders the bytes, validates, and returns on null error. On exhaustion, throws before the caller ever reaches write/cache. No off-by-one.

Safety invariants verified:

  • Invalid page = no write + no cache: translateValidated throws on exhaustion; the callers (translateMdxPage, translateReadme) have writeFileSync and cache-update AFTER the translateValidated call — unreachable on throw.
  • Retries don't increase concurrency: retries are serial inside one worker slot (correctly noted in workflow comment).
  • max_tokens truncation is NOT retried as a validity failure: no try/catch in the loop — transport/auth/max_tokens throws propagate directly.
  • Empty translation is retried: result.translated.trim() === "" catches whitespace-only output.
  • Token summation: inputTokens and outputTokens are accumulated across attempts (confirmed by test).

Frontmatter-less source fix (e7e6ca5): findFrontmatterError(rendered) runs unconditionally at line 88 of validate-translation.ts — BEFORE the sourceKeys branch. This means a malformed frontmatter block the model adds to a page that originally had NO frontmatter (like the README) is now caught. The commit message fix: validate rendered frontmatter for every source shape accurately describes this. The test "flags malformed frontmatter the model added to a frontmatter-less source" confirms the behavior.

Key parity design choice: Subset check only — rejects missing/renamed keys but tolerates extras. Rationale documented inline: an extra key is harmless (Mintlify ignores unknown frontmatter keys), while rejecting it would burn a retry (and risk exhausting the whole batch) on a page that deploys fine. This is a deliberate trade-off, clearly explained.

System prompt update (rule 3): The frontmatter quoting guidance has been extended to mirror the JSX attribute guidance from rule 2 — "never put an unescaped ASCII " inside them" — cutting the failure off at the source. Good preventive measure.

Workflow comment correction: The old comment said MAX_CONCURRENT=2 for 8 concurrent requests; corrected to MAX_CONCURRENT=4 (cli.ts default) for 16 concurrent requests. Verified — cli.ts defaults to TRANSLATE_MAX_CONCURRENT || 4. The retry clarification (serial in worker slot) is correct.


🧪 Evidence — Build & Test Results

Test Results — PR-specific tests
RUN  v4.1.10 /home/failproofai/failproofai

 ✓ __tests__/scripts/translate-docs/translator.test.ts (12 tests) 21ms
 ✓ __tests__/scripts/translate-docs/validate-translation.test.ts (9 tests) 72ms
 ✓ __tests__/scripts/validate-mdx.test.ts (22 tests) 99ms
 ✓ __tests__/scripts/translate-docs/mdx-translator.test.ts (52 tests) 154ms

 Test Files  4 passed (4)
      Tests  95 passed (95)
   Start at  21:25:10
   Duration  4.52s
TypeScript Compilation
npx tsc --noEmit
→ exit 0, no errors
validate:mdx on all 647 pages
bun scripts/validate-mdx.ts
→ ✓ 647 MDX page(s) parsed cleanly

CI results (from prior review): 2232 tests across 126 files, all green. Lint passes (0 errors). E2E tests pass.


🔗 Issue Linkage

No linked issue. The PR body describes the problem comprehensively — the daily translate-docs workflow kept failing (5 of last 15 runs) due to translator re-emitting unescaped inner quotes in frontmatter YAML, invisible to the repo's own validate:mdx because findMdxParseError blanked frontmatter before compiling. The fix is architectural: move validation to generation time with retry.


👥 Human Review Feedback

Review threads from previous review:

  1. @coderabbitai (thread PRRT_kwDOR6tUec6R0Rea) — requested: (a) change extra-key case to expect rejection, (b) add frontmatter-less source + malformed frontmatter test. Status: (a) The author chose subset parity (reject missing, tolerate extras) with documented rationale — a valid design choice. (b) Fixed in e7e6ca5findFrontmatterError runs unconditionally, and the test "flags malformed frontmatter the model added to a frontmatter-less source" covers this case. → Both addressed per author's design decisions.

  2. @coderabbitai (thread PRRT_kwDOR6tUec6R0Ref) — requested: validate rendered frontmatter before branching on source keys, require identical key sets. Status: The unconditional findFrontmatterError(rendered) call fixes the ordering issue. On key parity: the author chose subset check (reject missing, tolerate extras), documented inline with rationale. The test "allows an extra frontmatter key the source does not have" confirms this behavior. → Addressed per author's design decisions.

  3. @NiveditJain (human comment) — acknowledged coderabbit feedback, explained fixes and design rationale. No further requests. → ✅ Acknowledged.

Both coderabbit threads remain unresolved in the GitHub UI — these are bot-generated review threads that the human author has acknowledged and addressed (or made informed design decisions on). The author/maintainer should resolve them.


💡 Suggestions

  • Consider extracting FRONTMATTER_RE to a shared constant in validate-mdx.ts (or a shared module) rather than duplicating it in validate-translation.ts. The 13-character regex is unlikely to drift, but a single source of truth is cleaner.
  • The attempt(s) string in the exhaustion error could use proper pluralization.
  • The test file __tests__/scripts/translate-docs/validate-translation.test.ts could benefit from one additional edge case: a source WITH frontmatter where the translation produces malformed YAML on a line other than the first two (e.g., a new key with an unparseable complex value). The existing YAML-error test covers line 2; deeper lines might expose yaml library differences.

🏆 Verdict

VERDICT: APPROVED


Automated code review · 2026-07-17 · Hermes Agent

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Automated Code Review — PR #570 (Re-review)

📋 Executive Summary

This PR shifts docs translation validation from the final consolidate CI job to generation time with automatic retry. Every translated page is validated (frontmatter YAML + key parity + MDX body) against the exact bytes that will be written, and invalid output triggers a re-translate capped at 3 attempts. Two commits: the initial implementation (6000368) and a fix (e7e6ca5) that validates frontmatter for every source shape — even frontmatter-less sources — closing a blind spot identified in the prior review. Code is well-architected, the safety invariants are sound, and tests are comprehensive. No blocking issues found.


📊 Change Architecture

graph TD
    A["translateContent<br/>(raw LLM call)"] -->|"+ feedback param"| B["translateValidated<br/>(NEW: retry loop)"]
    B -->|render| C["render callback<br/>(sanitize + link-rewrite)"]
    C -->|validate| D["findTranslationError<br/>(NEW)"]
    D --> E["findFrontmatterError<br/>(NEW in validate-mdx)"]
    D --> F["frontmatterKeys parity<br/>(subset check)"]
    D --> G["findMdxParseError<br/>(existing)"]
    E -->|"YAML parse"| H["yaml@2.9.0<br/>(already a dep)"]
    B -->|"fail: throw before write"| I["No file written<br/>No cache entry"]
    B -->|"pass"| J["writeFileSync<br/>update cache"]
    K["translateMdxPage"] --> B
    L["translateReadme"] --> B
    style B fill:#90EE90
    style D fill:#90EE90
    style E fill:#90EE90
    style F fill:#87CEEB
    style G fill:#87CEEB
Loading

Legend: 🟢 New | 🔵 Modified | 🟡 Breaking Change Risk


🔴 Breaking Changes

✅ No breaking changes detected. All existing public APIs (translateMdxPage, translateReadme, TranslationResult) are backward-compatible. The new attempts field on TranslationResult is optional. The translateContent function gained an optional 5th parameter (feedback) that no existing callers pass. The CLI summary now prints retry counts but this is additive, not a format change.


⚠️ Issues Found

  1. 🔵 Info (non-blocking)scripts/translate-docs/validate-translation.ts:33 — The FRONTMATTER_RE regex is duplicated from scripts/validate-mdx.ts:49. The comment explains this is intentional (to avoid depending on an unexported internal constant), which is a reasonable design choice for a 13-character regex, but worth noting.

  2. 🔵 Info (non-blocking)scripts/translate-docs/translator.ts:251 — Error message uses attempt(s) instead of a proper plural: "after ${MAX_ATTEMPTS} attempt(s)". Should be "after ${MAX_ATTEMPTS} attempt${MAX_ATTEMPTS === 1 ? '' : 's'}" or simply "after ${MAX_ATTEMPTS} attempts" (since 1 would mean no retries needed, the exhaustion path only triggers with ≥ 1 failed attempt).


🔬 Logical / Bug Analysis

Retry loop correctness (translateValidated): The for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) loop properly bounds retries. Each iteration: calls translateContent, sums token usage, checks for empty output (re-treated as validity failure), renders the bytes, validates, and returns on null error. On exhaustion, throws before the caller ever reaches write/cache. No off-by-one.

Safety invariants verified:

  • Invalid page = no write + no cache: translateValidated throws on exhaustion; the callers (translateMdxPage, translateReadme) have writeFileSync and cache-update AFTER the translateValidated call — unreachable on throw.
  • Retries don't increase concurrency: retries are serial inside one worker slot (correctly noted in workflow comment).
  • max_tokens truncation is NOT retried as a validity failure: no try/catch in the loop — transport/auth/max_tokens throws propagate directly.
  • Empty translation is retried: result.translated.trim() === "" catches whitespace-only output.
  • Token summation: inputTokens and outputTokens are accumulated across attempts (confirmed by test).

Frontmatter-less source fix (e7e6ca5): findFrontmatterError(rendered) runs unconditionally at line 88 of validate-translation.ts — BEFORE the sourceKeys branch. This means a malformed frontmatter block the model adds to a page that originally had NO frontmatter (like the README) is now caught. The commit message fix: validate rendered frontmatter for every source shape accurately describes this. The test "flags malformed frontmatter the model added to a frontmatter-less source" confirms the behavior.

Key parity design choice: Subset check only — rejects missing/renamed keys but tolerates extras. Rationale documented inline: an extra key is harmless (Mintlify ignores unknown frontmatter keys), while rejecting it would burn a retry (and risk exhausting the whole batch) on a page that deploys fine. This is a deliberate trade-off, clearly explained.

System prompt update (rule 3): The frontmatter quoting guidance has been extended to mirror the JSX attribute guidance from rule 2 — "never put an unescaped ASCII " inside them" — cutting the failure off at the source. Good preventive measure.

Workflow comment correction: The old comment said MAX_CONCURRENT=2 for 8 concurrent requests; corrected to MAX_CONCURRENT=4 (cli.ts default) for 16 concurrent requests. Verified — cli.ts defaults to TRANSLATE_MAX_CONCURRENT || 4. The retry clarification (serial in worker slot) is correct.


🧪 Evidence — Build & Test Results

Test Results — PR-specific tests
RUN  v4.1.10 /home/failproofai/failproofai

 ✓ __tests__/scripts/translate-docs/translator.test.ts (12 tests) 21ms
 ✓ __tests__/scripts/translate-docs/validate-translation.test.ts (9 tests) 72ms
 ✓ __tests__/scripts/validate-mdx.test.ts (22 tests) 99ms
 ✓ __tests__/scripts/translate-docs/mdx-translator.test.ts (52 tests) 154ms

 Test Files  4 passed (4)
      Tests  95 passed (95)
   Start at  21:25:10
   Duration  4.52s
TypeScript Compilation
npx tsc --noEmit
→ exit 0, no errors
validate:mdx on all 647 pages
bun scripts/validate-mdx.ts
→ ✓ 647 MDX page(s) parsed cleanly

CI results (from prior review): 2232 tests across 126 files, all green. Lint passes (0 errors). E2E tests pass.


🔗 Issue Linkage

No linked issue. The PR body describes the problem comprehensively — the daily translate-docs workflow kept failing (5 of last 15 runs) due to translator re-emitting unescaped inner quotes in frontmatter YAML, invisible to the repo's own validate:mdx because findMdxParseError blanked frontmatter before compiling. The fix is architectural: move validation to generation time with retry.


👥 Human Review Feedback

Review threads from previous review:

  1. @coderabbitai (thread PRRT_kwDOR6tUec6R0Rea) — requested: (a) change extra-key case to expect rejection, (b) add frontmatter-less source + malformed frontmatter test. Status: (a) The author chose subset parity (reject missing, tolerate extras) with documented rationale — a valid design choice. (b) Fixed in e7e6ca5findFrontmatterError runs unconditionally, and the test "flags malformed frontmatter the model added to a frontmatter-less source" covers this case. → Both addressed per author's design decisions.

  2. @coderabbitai (thread PRRT_kwDOR6tUec6R0Ref) — requested: validate rendered frontmatter before branching on source keys, require identical key sets. Status: The unconditional findFrontmatterError(rendered) call fixes the ordering issue. On key parity: the author chose subset check (reject missing, tolerate extras), documented inline with rationale. The test "allows an extra frontmatter key the source does not have" confirms this behavior. → Addressed per author's design decisions.

  3. @NiveditJain (human comment) — acknowledged coderabbit feedback, explained fixes and design rationale. No further requests. → ✅ Acknowledged.

Both coderabbit threads remain unresolved in the GitHub UI — these are bot-generated review threads that the human author has acknowledged and addressed (or made informed design decisions on). The author/maintainer should resolve them.


💡 Suggestions

  • Consider extracting FRONTMATTER_RE to a shared constant in validate-mdx.ts (or a shared module) rather than duplicating it in validate-translation.ts. The 13-character regex is unlikely to drift, but a single source of truth is cleaner.
  • The attempt(s) string in the exhaustion error could use proper pluralization.
  • The test file __tests__/scripts/translate-docs/validate-translation.test.ts could benefit from one additional edge case: a source WITH frontmatter where the translation produces malformed YAML on a line other than the first two (e.g., a new key with an unparseable complex value). The existing YAML-error test covers line 2; deeper lines might expose yaml library differences.

🏆 Verdict

VERDICT: APPROVED


Automated code review · 2026-07-17 · Hermes Agent

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@hermes-exosphere Thanks for the thorough re-review and concrete verification evidence.

The report correctly reflects the current state: rendered-byte validation, bounded retries, no write/cache on exhaustion, and unconditional rendered-frontmatter validation—including frontmatter-less sources—are all covered. The remaining regex-deduplication and attempt(s) wording notes are non-blocking polish items.

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: Approved. ✅

@NiveditJain
NiveditJain merged commit 5670848 into main Jul 17, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants