Skip to content

feat: docs build verification and code sample checking - #70

Open
Benkapner wants to merge 1 commit into
mainfrom
feat/build-verification
Open

feat: docs build verification and code sample checking#70
Benkapner wants to merge 1 commit into
mainfrom
feat/build-verification

Conversation

@Benkapner

Copy link
Copy Markdown
Collaborator

Summary

Adds pre-commit verification for generated documentation.

  • Docs build check (src/build_check.py): optional docs-build-command input runs the docs build in a temp copy before opening a PR. Hard timeout, sanitized output.
  • Code sample checking: extracts fenced code blocks and syntax-checks python (compile), json (json.loads), yaml (yaml.safe_load). Reports failures without executing anything.

Test plan

  • uv run pytest -v passes (428 tests)
  • Lint clean
  • Build timeout is handled gracefully
  • Invalid Python/JSON code samples are detected

The pipeline can only produce plausible text. An optional
docs-build-command input runs the build against the generated tree
before opening a PR. On failure, reports the error. Also extracts
fenced code blocks and syntax-checks python, json, and yaml using
stdlib parsers (compile, json.loads, yaml.safe_load). Reports failures
but does not execute anything.
@Benkapner
Benkapner requested a review from csoceanu August 17, 2026 05:50
@Benkapner Benkapner self-assigned this Aug 17, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 17, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:51 AM UTC · Completed 6:07 AM UTC

Commit: c4f5018 · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

High

  • [dead code / consumer completeness] src/build_check.py:1 — The new module build_check.py is never imported or called by any other module in the codebase. The DOCS_BUILD_COMMAND environment variable is plumbed through action.yml to the container, but suggest_docs.py (the main orchestrator) does not import run_docs_build or check_code_samples. Setting docs-build-command in a workflow has no effect — the build check will never run. The action.yml input creates a false contract: users will believe their build command is being executed, but it silently does nothing.
    Remediation: Import and call run_docs_build and check_code_samples at the appropriate point in suggest_docs.py. Read DOCS_BUILD_COMMAND from os.environ and invoke run_docs_build(build_command, docs_root). Handle failure by feeding the error output back into regeneration (as the action.yml description promises) or aborting the push.

Medium

  • [missing-authorization] — This PR introduces a new feature (docs build verification and code sample checking) with no linked issue. The change adds a new source module, a new action input, a new environment variable, and a test file.
    Remediation: Create a GitHub issue describing the feature requirements and link it to this PR.

  • [secrets handling] src/build_check.py:28 — The subprocess.run() call with shell=True inherits the full process environment, including GH_TOKEN, MODEL_API_KEY, JIRA_API_TOKEN, and GOOGLE_SA_KEY. While the build_command is set by the repo owner (trusted input), the command runs against a docs tree that may contain LLM-generated content from untrusted PR diffs. A sanitized environment would reduce the blast radius.
    Remediation: Pass a sanitized environment to subprocess.run() that strips sensitive variables: env = {k: v for k, v in os.environ.items() if k not in ('GH_TOKEN', 'MODEL_API_KEY', 'JIRA_API_TOKEN', 'GOOGLE_SA_KEY')}.

  • [command injection] src/build_check.py:28subprocess.run() is called with shell=True and the build_command string is passed directly. While docs-build-command comes from workflow YAML (trusted input), using shell=True means the command is interpreted by /bin/sh. The existing run_command_safe() in security_utils.py does not use shell=True.
    Remediation: Add a comment documenting the trust boundary. Consider validating against an allowlist of known build tools or using shlex.split() and shell=False.

  • [logic error] src/build_check.py:49 — The _FENCE_PATTERN regex only matches backtick-delimited code fences. The codebase recognizes both backtick and tilde fences (comments.py handles both), and tilde fences are valid Markdown. Any fenced code block using ~~~python or ~~~json will be silently skipped by check_code_samples, producing false negatives.
    Remediation: Update _FENCE_PATTERN to also match tilde fences: r'^(?:```|~~~)(\w+)\s*\n(.*?)^(?:```|~~~)\s*$'.

  • [edge case] src/build_check.py:27 — When subprocess.run raises TimeoutExpired with shell=True, the shell process is killed but its child processes may continue running as orphans, consuming CPU/memory on the runner even after the timeout is reported.
    Remediation: Use start_new_session=True with os.killpg to ensure the entire process tree is terminated on timeout.

  • [missing-docs] CLAUDE.md:11 — The 'Source modules (src/)' table does not include the new build_check.py module. This table is the authoritative module reference and is now incomplete.
    Remediation: Add a row for build_check.py to the module table.

  • [missing-docs] CLAUDE.md:57 — The 'Environment variables' table does not include the new DOCS_BUILD_COMMAND variable. Every other env var mapped in action.yml has a corresponding row.
    Remediation: Add a row for DOCS_BUILD_COMMAND.

  • [missing-docs] README.md:193 — The 'Optional Action Inputs' section does not document the new docs-build-command input. Users relying on this table to discover available inputs will not find the new option.
    Remediation: Add docs-build-command to the optional inputs table.

Low

  • [missing-docs] README.md:152 — The example workflow YAML does not include docs-build-command. The other optional input style-config-path is shown, creating an inconsistency.

  • [import style] src/build_check.py:13 — Other modules in src/ use grouping comments for internal imports (e.g., # Import security utilities). This module omits the comment.

  • [function docstring style] src/build_check.py:17 — The run_docs_build docstring uses a terse Returns (success, error_output) format rather than the Returns: block style used elsewhere.

  • [function docstring style] src/build_check.py:55 — Both check_code_samples and run_docs_build omit Args: blocks. Many multi-parameter functions in the codebase include them.

  • [edge case] src/build_check.py:36 — Error output is truncated to 2000 characters before sanitize_output is called. If a sensitive token straddles the boundary, it could avoid scrubbing.
    Remediation: Call sanitize_output before truncating: sanitize_output(error)[:2000].

  • [test adequacy] tests/test_build_check.py:1 — The test suite does not cover tilde-fenced code blocks, YAML validation, empty code blocks, or blocks without a language specifier.

  • [secrets handling] src/build_check.py:23shutil.copytree() copies the entire docs_root into a temp directory. If docs_root contains sensitive files, they are accessible during the build window.


Labels: PR adds a new feature module and modifies GitHub Action inputs


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread src/build_check.py
@@ -0,0 +1,87 @@
"""Pre-commit verification for generated documentation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] dead code / consumer completeness

The new module build_check.py is never imported or called by any other module in the codebase. The DOCS_BUILD_COMMAND environment variable is plumbed through action.yml to the container, but suggest_docs.py (the main orchestrator) does not import run_docs_build or check_code_samples. Setting docs-build-command in a workflow has no effect -- the build check will never run. The action.yml input creates a false contract.

Suggested fix: Import and call run_docs_build and check_code_samples at the appropriate point in suggest_docs.py. Read DOCS_BUILD_COMMAND from os.environ and invoke run_docs_build(build_command, docs_root). Handle failure by feeding the error output back into regeneration or aborting the push.

Comment thread src/build_check.py
try:
shutil.copytree(docs_root, tmpdir, dirs_exist_ok=True)
result = subprocess.run(
build_command,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] secrets handling

The subprocess.run() call with shell=True inherits the full process environment, including GH_TOKEN, MODEL_API_KEY, JIRA_API_TOKEN, and GOOGLE_SA_KEY. While the build_command is set by the repo owner (trusted input), the command runs against a docs tree that may contain LLM-generated content from untrusted PR diffs. A sanitized environment would reduce the blast radius.

Suggested fix: Pass a sanitized environment to subprocess.run() that strips sensitive variables: env = {k: v for k, v in os.environ.items() if k not in ('GH_TOKEN', 'MODEL_API_KEY', 'JIRA_API_TOKEN', 'GOOGLE_SA_KEY')}

Comment thread src/build_check.py
try:
shutil.copytree(docs_root, tmpdir, dirs_exist_ok=True)
result = subprocess.run(
build_command,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] command injection

subprocess.run() is called with shell=True and the build_command string is passed directly. While docs-build-command comes from workflow YAML (trusted input), using shell=True means the command is interpreted by /bin/sh. The existing run_command_safe() in security_utils.py does not use shell=True.

Suggested fix: Add a comment documenting the trust boundary. Consider validating against an allowlist of known build tools or using shlex.split() and shell=False.

Comment thread src/build_check.py

_FENCE_PATTERN = re.compile(
r"^```(\w+)\s*\n(.*?)^```\s*$",
re.MULTILINE | re.DOTALL,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] logic error

The _FENCE_PATTERN regex only matches backtick-delimited code fences. The codebase recognizes both backtick and tilde fences (comments.py handles both), and tilde fences are valid Markdown. Any fenced code block using ~~~python or ~~~json will be silently skipped by check_code_samples, producing false negatives.

Suggested fix: Update _FENCE_PATTERN to also match tilde fences: r'^(?:|~~~)(\w+)\s*\n(.*?)^(?:|~~~)\s*$'

Comment thread src/build_check.py
tmpdir = tempfile.mkdtemp(prefix="code-to-docs-build-")
try:
shutil.copytree(docs_root, tmpdir, dirs_exist_ok=True)
result = subprocess.run(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] edge case

When subprocess.run raises TimeoutExpired with shell=True, the shell process is killed but its child processes may continue running as orphans, consuming CPU/memory on the runner even after the timeout is reported.

Suggested fix: Use start_new_session=True with os.killpg to ensure the entire process tree is terminated on timeout.

Comment thread src/build_check.py


def run_docs_build(build_command, docs_root, timeout=120):
"""Run a docs build command in a temp copy of the docs tree.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] function docstring style

The run_docs_build docstring uses a terse Returns (success, error_output) format rather than the Returns: block style used elsewhere.

Comment thread src/build_check.py

def check_code_samples(content, file_path=""):
"""Syntax-check fenced code blocks in generated documentation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] function docstring style

Both check_code_samples and run_docs_build omit Args: blocks. Many multi-parameter functions in the codebase include them.

Comment thread src/build_check.py
timeout=timeout,
)
if result.returncode != 0:
error = (result.stderr or result.stdout or "Build failed").strip()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] edge case

Error output is truncated to 2000 characters before sanitize_output is called. If a sensitive token straddles the boundary, it could avoid scrubbing.

Suggested fix: Call sanitize_output before truncating: sanitize_output(error)[:2000].

Comment thread tests/test_build_check.py
@@ -0,0 +1,59 @@
"""Tests for build_check.py -- docs build verification and code sample checking."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] test adequacy

The test suite does not cover tilde-fenced code blocks, YAML validation, empty code blocks, or blocks without a language specifier.

Comment thread src/build_check.py
"""
if not build_command:
return True, ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] secrets handling

shutil.copytree() copies the entire docs_root into a temp directory. If docs_root contains sensitive files, they are accessible during the build window.

@fullsend-ai-review fullsend-ai-review Bot added feature python Pull requests that update python code labels Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant