Skip to content

feat(scaffold): auto-detect and install pre-commit tool dependencies - #1055

Merged
waynesun09 merged 1 commit into
mainfrom
auto-precommit-tools
Jun 25, 2026
Merged

feat(scaffold): auto-detect and install pre-commit tool dependencies#1055
waynesun09 merged 1 commit into
mainfrom
auto-precommit-tools

Conversation

@waynesun09

@waynesun09 waynesun09 commented May 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a registry-based system (.pre-commit-tools.yaml) that maps pre-commit hook repos/IDs to the system tools they require, with pinned versions and SHA256 checksums
  • Add resolve-precommit-tools.py to parse a target repo's .pre-commit-config.yaml and produce a JSON manifest of needed tools
  • Add install-precommit-tools.sh to install tools from the manifest with architecture detection and checksum verification
  • Integrate into pre-scripts (pre-code.sh, pre-fix.sh) so tools are installed before the sandbox runs
  • Replace hardcoded lychee/uv install blocks in post-scripts (post-code.sh, post-fix.sh) with the same auto-resolve mechanism as a fallback
  • Gitleaks install stays hardcoded in post-scripts since it serves as a security gate (secret scan), not just a pre-commit dependency

Motivation

Post-scripts run authoritative pre-commit hooks after the sandbox exits. If a target repo's .pre-commit-config.yaml requires tools not baked into the sandbox image (e.g., lychee, shellcheck, actionlint), the hooks fail and block the push. The previous approach of manually adding tool installs to post-scripts doesn't scale — every new tool needs a code change.

This auto-detection system reads the target repo's hook config and installs what's needed, with the same supply-chain security (pinned versions, SHA256 checksums) as the hardcoded installs it replaces.

Related: #1270 (expanding registry coverage — follow-up work after this lands)

Test plan

  • Verify resolve-precommit-tools.py correctly parses fullsend's own .pre-commit-config.yaml and produces valid JSON manifest
  • Verify install-precommit-tools.sh installs binary tools with checksum verification
  • Verify pre-scripts call the resolve/install chain when .pre-commit-config.yaml exists
  • Verify post-scripts use the auto-resolve fallback and no longer reference LYCHEE_VERSION/UV_VERSION
  • Verify gitleaks install remains hardcoded in post-scripts (security gate)
  • Run make lint — passes
  • Deploy to nonflux org and trigger code/fix agents on a repo with custom pre-commit hooks

@github-actions

github-actions Bot commented May 16, 2026

Copy link
Copy Markdown

Site preview

Preview: https://499874db-site.fullsend-ai.workers.dev

Commit: 228b9e5bb7cd30cc45f90c3807c2b605038eea1a

@fullsend-ai-review

fullsend-ai-review Bot commented May 16, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [supply-chain] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:18 — PyYAML is installed via pip install --quiet --no-deps --break-system-packages pyyaml==6.0.2 without --require-hashes. Version pinning alone does not prevent a compromised PyPI mirror or MITM from serving a malicious wheel with a matching version string. This script runs on the GHA runner with access to PUSH_TOKEN, so a compromised PyYAML could influence which tools get installed. The prior review marked this as resolved (claiming --require-hashes was added), but the current code does not include hash verification — this appears to have regressed.
    Remediation: Add --require-hashes to the pip install call and provide the SHA256 hash(es) for the pyyaml==6.0.2 wheel(s), matching the supply-chain hygiene used for binary tool downloads elsewhere in this PR.

Low

  • [missing-authorization] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — This PR adds ~606 lines across 8 files with no linked authorizing issue. The PR body references Expand precommit-tools.yaml registry coverage #1270 as follow-up work, but Expand precommit-tools.yaml registry coverage #1270 was created in the context of this PR, not as prior authorization. The PR has a thorough motivation section and has been through multiple review iterations, which provides practical context — but a retroactive issue documenting the problem and design decision would strengthen provenance.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:173 — The default GOARCH for x86_64 is set to "x64", which is non-standard (Go uses "amd64"). Currently no registry tool hits this default without a goarch_override, but any future tool using {goarch} in its URL template without an override would get "x64" substituted, likely producing a 404.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:63 — Duplicate (repo, hook_id) keys in the registry silently overwrite earlier entries via repo_hook_map[key] = tool. Not currently triggered (the two shellcheck entries have different repo values) but a latent bug if the registry grows.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check (tool --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) could match a library version triple in the output instead of the tool's own version, causing a spurious "already installed" skip.

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The existing post-code.sh hard-fails (exit 1) on unsupported architectures for lychee. The new system silently skips with a ::warning::. Practical risk is negligible since GHA runners are exclusively x86_64 or aarch64, but the behavioral change should be intentional.

  • [variable-naming-inconsistency] internal/scaffold/fullsend-repo/scripts/post-code.sh:424SCRIPT_DIR_POST differs from the SCRIPT_DIR convention. The _POST suffix is needed in post-fix.sh (which already defines SCRIPT_DIR for another purpose), but post-code.sh has no such collision — the naming is carried across both files for consistency.

  • [layering-coherence] internal/scaffold/fullsend-repo/scripts/.pre-commit-tools.yaml — Per ADR 0035, scripts/ is a layered directory. Org customization requires overriding the entire registry file in customized/scripts/, which doesn't compose well for partial overrides (e.g., adding one tool requires duplicating the entire upstream registry).

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/.pre-commit-tools.yaml:67 — The gitleaks entry with skip_install: true expands the registry's responsibility beyond install mapping to include hook exemption tracking.

  • [test-operator-inconsistency] internal/scaffold/fullsend-repo/scripts/pre-code.sh:633 — New code uses [ for test operators while the existing file exclusively uses [[. Same pattern in pre-fix.sh:672.

Resolved from prior review
  • [error-handling] Medium → Fixed: resolve-precommit-tools.py failure now truncates the manifest, preventing partial JSON from passing downstream validation.
  • [scope-tier-mismatch] Medium → Dropped: The PR adds genuine new capability (shellcheck and actionlint auto-detection for repos whose pre-commit hooks require them). feat is appropriate per COMMITS.md.
  • [architectural-fit] Medium → Regressed: PyYAML --require-hashes fix was previously marked resolved but current code does not include hash verification. Re-raised as [supply-chain] medium finding above.
Previous run

Review

Findings

Medium

Low

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:56 — Duplicate (repo, hook_id) keys in the registry silently overwrite earlier entries via repo_hook_map[key] = tool. Not currently triggered but a latent bug if the registry grows.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check (tool --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) could match a library version triple in the output instead of the tool's own version, causing a spurious "already installed" skip.

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The existing post-code.sh hard-fails (exit 1) on unsupported architectures for lychee. The new system silently skips with a ::warning::. Practical risk is negligible since GHA runners are exclusively x86_64 or aarch64, but the behavioral change should be intentional.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The gitleaks entry with skip_install: true expands the registry's responsibility beyond install mapping to include hook exemption tracking. Consider clarifying the registry's single responsibility.

  • [layering-coherence] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — Per ADR 0035, scripts/ is a layered directory where upstream defaults are provided at runtime. The registry is added as upstream-provided layered content, which is coherent if it serves as a shared default. However, if orgs need to customize tool versions or add org-specific tools, they would override the entire file in customized/scripts/, which doesn't compose well. Consider documenting whether org-level registry extension is a design goal.

Previous run (2)

Review

Findings

Medium

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-code.sh — When resolve-precommit-tools.py fails, the || branch prints a warning but stdout may contain partial/corrupt JSON already written to $MANIFEST. The downstream jq -e check catches full corruption and safely skips install, but partial valid JSON could pass validation, causing unpredictable behavior. The feature silently does nothing when the resolver fails — tools that should have been installed are not. Same pattern in post-fix.sh, pre-code.sh, pre-fix.sh.
    Remediation: Truncate the manifest on failure: python3 ... > "${MANIFEST}" || { echo '::warning::...'; : > "${MANIFEST}"; }

  • [scope-tier-mismatch] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — PR title uses feat(scaffold) but the change primarily replaces hardcoded tool-install blocks with a registry-driven mechanism. The same tools (lychee, uv) get installed via a different internal path — end users see no new capability. Per COMMITS.md, restructuring internals without user-visible behavior change is refactor, not feat. GoReleaser uses commit prefixes to populate release notes; feat goes into the Features section that end users read.
    Remediation: Change PR title and commit prefix to refactor(scaffold): extract tool installation into registry-based system.

  • [architectural-fit] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:22 — The resolver auto-installs PyYAML 6.0.2 via pip install --quiet --no-deps pyyaml==6.0.2 without --require-hashes. The existing post-code.sh and post-fix.sh use pinned versions with SHA256 checksums for all binary downloads (gitleaks, lychee, uv). This departs from the project's established supply-chain hygiene. The script runs on the GHA runner (not in the sandbox) with access to PUSH_TOKEN.
    Remediation: Pin PyYAML with --require-hashes and the known SHA256 of the wheel, pre-install in the runner image, or use stdlib-only parsing.

  • [missing-authorization] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — This PR adds ~595 lines across 8 files with no linked issue. The change introduces a registry-based pre-commit tool installation system — a non-trivial infrastructure change that would benefit from an issue documenting the problem, design decision, and maintainer approval.
    Remediation: Link to an existing issue or create one documenting the motivation and design.

Low

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The existing post-code.sh hard-fails (exit 1) on unsupported architecture for lychee. The new system silently skips with a warning.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:56 — Duplicate (repo, hook_id) keys in the registry would silently overwrite earlier entries. Not currently triggered but a latent bug.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check could match a library version triple instead of the tool version.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The gitleaks entry with skip_install: true expands the registry's responsibility to include recognition of externally-managed tools.

Previous run (3)

Review

Findings

Medium

  • [injection-vuln] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:65 — The warnings loop sanitizes :: sequences (line 64: w="${w//::/ }") but does not sanitize literal newlines. Warning text from resolve-precommit-tools.py interpolates hook_id and entry values from the target repo's .pre-commit-config.yaml (untrusted). A malicious .pre-commit-config.yaml could embed a literal newline in a hook_id. The value flows through json.dumps (escapes \n) then jq -r (unescapes back to literal newline), producing a multi-line echo that injects arbitrary GHA workflow commands (e.g., ::add-mask::).
    Remediation: Strip newlines and carriage returns in addition to ::: w="${w//$'\n'/ }"; w="${w//$'\r'/ }"

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-code.sh:311 — When resolve-precommit-tools.py fails, the || branch prints a warning but stdout may contain partial/corrupt JSON written to $MANIFEST before the failure. The downstream jq -e check catches corruption and safely skips install, but the feature silently does nothing — tools that should have been installed are not. Same pattern in post-fix.sh, pre-code.sh, pre-fix.sh.
    Remediation: Truncate the manifest on failure: python3 ... > "${MANIFEST}" || { echo '::warning::...'; : > "${MANIFEST}"; }

  • [architectural-fit] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:22 — The resolver auto-installs PyYAML 6.0.2 via pip if not present, without --require-hashes. This introduces a new pattern (auto-installing runtime dependencies) that departs from the project's established supply-chain hygiene (pinned binary downloads with SHA256 verification).
    Remediation: Consider pre-installing PyYAML in the runner image, or add --hash to the pip install.

  • [comment-reference] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:10 — Multiple file comments reference resolve-precommit-tools.sh but the actual script is resolve-precommit-tools.py. Appears in install-precommit-tools.sh (lines 10, 24) and precommit-tools.yaml (lines 5, 32).
    Remediation: Update all references from .sh to .py.

  • [missing-authorization] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — This PR introduces a registry-based system (577 additions across 8 files) with no linked issue. Non-trivial changes require explicit authorization.
    Remediation: Link to an existing issue or create one.

  • [scope-tier-mismatch] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — PR title claims feat(scaffold) but the change primarily replaces existing hardcoded tool installation with a declarative registry system. Per COMMITS.md, restructuring internals is refactor:, not feat:. GoReleaser uses commit prefixes for release notes.
    Remediation: Consider refactor(scaffold): if the primary change is restructuring install logic.

Low

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:171 — The extra_binaries iteration could process empty strings from jq output. Add a guard: [ -z "${extra}" ] && continue.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:56 — Duplicate (repo, hook_id) keys in the registry would silently overwrite earlier entries. Not currently triggered but a latent bug.

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The old code hard-failed (exit 1) on unsupported architecture for lychee. The new system silently skips with a warning.

  • [race-condition] internal/scaffold/fullsend-repo/scripts/pre-code.sh:499 — Pre-scripts may run before the target repo checkout, causing the tool-install block to be silently skipped. Post-scripts provide the authoritative fallback.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The gitleaks entry with skip_install: true expands the registry's responsibility to include recognition of externally-managed tools.

  • [variable-naming] internal/scaffold/fullsend-repo/scripts/post-code.sh:287SCRIPT_DIR_POST deviates from the established SCRIPT_DIR pattern.

  • [naming-alignment] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py — New files consistently use precommit (no hyphen) while upstream uses pre-commit.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check could match a library version triple instead of the tool version.

  • [docstring-formatting] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:3 — Python docstring lacks a blank line after the summary line (PEP 257).

Previous run (4)

Review

Findings

Low

  • [correctness] tools/precommit-tools.yaml:52 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install extracted the binary from the tarball root with no directory prefix (tar xzf ... -C "$HOME/.local/bin" lychee). If the lychee tarball places the binary at the root (no subdirectory), the install script falls back to find to locate it, producing a spurious ::warning::Binary not found at expected path message on every install. Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.

  • [correctness] scripts/resolve-precommit-tools.sh:77 — PyYAML is installed with an unpinned version floor (pip install "pyyaml>=6.0"). Every other dependency in this PR uses pinned versions with SHA256 checksums for supply-chain safety. While a compromised PyYAML could only influence tool selection (not the tool binaries themselves, which are checksum-verified), pinning to a specific version (e.g., pyyaml==6.0.2) would be consistent with the supply-chain posture established by the rest of this change.

Previous run (5)

Review

Findings

Medium

  • [correctness] post-code.sh, post-fix.sh, pre-code.sh, pre-fix.sh (all 4 call sites) — All callers redirect stderr into the manifest file with bash "${RESOLVE_SCRIPT}" ... > "${MANIFEST}" 2>&1 || true. The resolve script writes diagnostic messages to stderr (e.g., ::error:: and ::warning:: annotations, PyYAML install output). When any stderr output is present, it gets prepended to the JSON manifest, corrupting it. The downstream jq -e validation catches the corruption and safely skips the install, but the feature silently does nothing — tools that should have been installed are not.
    Remediation: Change 2>&1 to 2>/dev/null (if stderr diagnostics are disposable) or redirect stderr to a separate file and log it.

Low

  • [correctness] tools/precommit-tools.yaml:52 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install (tar xzf ... -C "/sandbox/.local/bin" lychee) extracts the binary from the tarball root with no directory prefix. If the lychee tarball places the binary at the root (no subdirectory), the install script falls back to find to locate it, producing a spurious ::warning::Binary not found at expected path message on every install. Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.
Previous run (6)

Review

Findings

Medium

  • [injection-vuln] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:65 — The warnings loop sanitizes :: sequences (line 64: w="${w//::/ }") but does not sanitize literal newlines. Warning text from resolve-precommit-tools.py interpolates hook_id and entry values from the target repo's .pre-commit-config.yaml (untrusted). A malicious .pre-commit-config.yaml could embed a literal newline in a hook_id. The value flows through json.dumps (escapes \n) then jq -r (unescapes back to literal newline), producing a multi-line echo that injects arbitrary GHA workflow commands (e.g., ::add-mask::).
    Remediation: Strip newlines and carriage returns in addition to ::: w="${w//$'\n'/ }"; w="${w//$'\r'/ }"

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-code.sh:311 — When resolve-precommit-tools.py fails, the || branch prints a warning but stdout may contain partial/corrupt JSON written to $MANIFEST before the failure. The downstream jq -e check catches corruption and safely skips install, but the feature silently does nothing — tools that should have been installed are not. Same pattern in post-fix.sh, pre-code.sh, pre-fix.sh.
    Remediation: Truncate the manifest on failure: python3 ... > "${MANIFEST}" || { echo '::warning::...'; : > "${MANIFEST}"; }

  • [architectural-fit] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:22 — The resolver auto-installs PyYAML 6.0.2 via pip if not present, without --require-hashes. This introduces a new pattern (auto-installing runtime dependencies) that departs from the project's established supply-chain hygiene (pinned binary downloads with SHA256 verification).
    Remediation: Consider pre-installing PyYAML in the runner image, or add --hash to the pip install.

  • [comment-reference] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:10 — Multiple file comments reference resolve-precommit-tools.sh but the actual script is resolve-precommit-tools.py. Appears in install-precommit-tools.sh (lines 10, 24) and precommit-tools.yaml (lines 5, 32).
    Remediation: Update all references from .sh to .py.

  • [missing-authorization] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — This PR introduces a registry-based system (577 additions across 8 files) with no linked issue. Non-trivial changes require explicit authorization.
    Remediation: Link to an existing issue or create one.

  • [scope-tier-mismatch] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — PR title claims feat(scaffold) but the change primarily replaces existing hardcoded tool installation with a declarative registry system. Per COMMITS.md, restructuring internals is refactor:, not feat:. GoReleaser uses commit prefixes for release notes.
    Remediation: Consider refactor(scaffold): if the primary change is restructuring install logic.

Low

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:171 — The extra_binaries iteration could process empty strings from jq output. Add a guard: [ -z "${extra}" ] && continue.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:56 — Duplicate (repo, hook_id) keys in the registry would silently overwrite earlier entries. Not currently triggered but a latent bug.

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The old code hard-failed (exit 1) on unsupported architecture for lychee. The new system silently skips with a warning.

  • [race-condition] internal/scaffold/fullsend-repo/scripts/pre-code.sh:499 — Pre-scripts may run before the target repo checkout, causing the tool-install block to be silently skipped. Post-scripts provide the authoritative fallback.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The gitleaks entry with skip_install: true expands the registry's responsibility to include recognition of externally-managed tools.

  • [variable-naming] internal/scaffold/fullsend-repo/scripts/post-code.sh:287SCRIPT_DIR_POST deviates from the established SCRIPT_DIR pattern.

  • [naming-alignment] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py — New files consistently use precommit (no hyphen) while upstream uses pre-commit.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check could match a library version triple instead of the tool version.

  • [docstring-formatting] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:3 — Python docstring lacks a blank line after the summary line (PEP 257).

Previous run (7)

Review

Findings

Low

  • [correctness] tools/precommit-tools.yaml:52 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install extracted the binary from the tarball root with no directory prefix (tar xzf ... -C "$HOME/.local/bin" lychee). If the lychee tarball places the binary at the root (no subdirectory), the install script falls back to find to locate it, producing a spurious ::warning::Binary not found at expected path message on every install. Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.

  • [correctness] scripts/resolve-precommit-tools.sh:77 — PyYAML is installed with an unpinned version floor (pip install "pyyaml>=6.0"). Every other dependency in this PR uses pinned versions with SHA256 checksums for supply-chain safety. While a compromised PyYAML could only influence tool selection (not the tool binaries themselves, which are checksum-verified), pinning to a specific version (e.g., pyyaml==6.0.2) would be consistent with the supply-chain posture established by the rest of this change.

Previous run (8)

Review

Findings

Medium

  • [correctness] post-code.sh, post-fix.sh, pre-code.sh, pre-fix.sh (all 4 call sites) — All callers redirect stderr into the manifest file with bash "${RESOLVE_SCRIPT}" ... > "${MANIFEST}" 2>&1 || true. The resolve script writes diagnostic messages to stderr (e.g., ::error:: and ::warning:: annotations, PyYAML install output). When any stderr output is present, it gets prepended to the JSON manifest, corrupting it. The downstream jq -e validation catches the corruption and safely skips the install, but the feature silently does nothing — tools that should have been installed are not.
    Remediation: Change 2>&1 to 2>/dev/null (if stderr diagnostics are disposable) or redirect stderr to a separate file and log it.

Low

  • [correctness] tools/precommit-tools.yaml:52 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install (tar xzf ... -C "/sandbox/.local/bin" lychee) extracts the binary from the tarball root with no directory prefix. If the lychee tarball places the binary at the root (no subdirectory), the install script falls back to find to locate it, producing a spurious ::warning::Binary not found at expected path message on every install. Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.

Labels: PR modifies scaffold harness scripts (pre/post-code.sh, pre/post-fix.sh) and adds new runner-side tooling

Previous run

Review

Findings

Medium

Low

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:56 — Duplicate (repo, hook_id) keys in the registry silently overwrite earlier entries via repo_hook_map[key] = tool. Not currently triggered but a latent bug if the registry grows.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check (tool --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) could match a library version triple in the output instead of the tool's own version, causing a spurious "already installed" skip.

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The existing post-code.sh hard-fails (exit 1) on unsupported architectures for lychee. The new system silently skips with a ::warning::. Practical risk is negligible since GHA runners are exclusively x86_64 or aarch64, but the behavioral change should be intentional.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The gitleaks entry with skip_install: true expands the registry's responsibility beyond install mapping to include hook exemption tracking. Consider clarifying the registry's single responsibility.

  • [layering-coherence] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — Per ADR 0035, scripts/ is a layered directory where upstream defaults are provided at runtime. The registry is added as upstream-provided layered content, which is coherent if it serves as a shared default. However, if orgs need to customize tool versions or add org-specific tools, they would override the entire file in customized/scripts/, which doesn't compose well. Consider documenting whether org-level registry extension is a design goal.

Info

  • [injection-vuln] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:64 — Warning sanitization handles :: sequences and newline-splitting (via the while/read loop) but does not strip ANSI escape sequences. Attack surface is limited since data originates from the target repo's own .pre-commit-config.yaml and ::warning:: does not URL-decode %0A.

  • [command-injection] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:207NAME and VERSION are passed to apt-get/pip/npm without shell-safe character validation. Currently sourced from the trusted committed registry, so risk is negligible.

  • [permission-expansion] internal/scaffold/scaffold.go:45 — Two new files registered as executable: install-precommit-tools.sh and resolve-precommit-tools.py. These scripts run on the GHA runner with access to PUSH_TOKEN. Expected for the feature being added.

Resolved from prior review
  • [error-handling] Medium → Fixed: resolve-precommit-tools.py failure now truncates the manifest (: > "${MANIFEST}"), preventing partial JSON from passing downstream validation.
  • [architectural-fit] Medium → Fixed: PyYAML install now uses --require-hashes with specific SHA256 hashes for supply-chain safety.
  • [scope-tier-mismatch] Medium → Dropped: The PR adds genuine new capability (shellcheck and actionlint auto-detection for repos whose pre-commit hooks require them). feat is appropriate per COMMITS.md.

Labels: PR modifies scaffold harness scripts (pre/post-code.sh, pre/post-fix.sh) and adds new runner-side tooling.

Previous run (2)

Review

Findings

Medium

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-code.sh — When resolve-precommit-tools.py fails, the || branch prints a warning but stdout may contain partial/corrupt JSON already written to $MANIFEST. The downstream jq -e check catches full corruption and safely skips install, but partial valid JSON could pass validation, causing unpredictable behavior. The feature silently does nothing when the resolver fails — tools that should have been installed are not. Same pattern in post-fix.sh, pre-code.sh, pre-fix.sh.
    Remediation: Truncate the manifest on failure: python3 ... > "${MANIFEST}" || { echo '::warning::...'; : > "${MANIFEST}"; }

  • [scope-tier-mismatch] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — PR title uses feat(scaffold) but the change primarily replaces hardcoded tool-install blocks with a registry-driven mechanism. The same tools (lychee, uv) get installed via a different internal path — end users see no new capability. Per COMMITS.md, restructuring internals without user-visible behavior change is refactor, not feat. GoReleaser uses commit prefixes to populate release notes; feat goes into the Features section that end users read.
    Remediation: Change PR title and commit prefix to refactor(scaffold): extract tool installation into registry-based system.

  • [architectural-fit] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:22 — The resolver auto-installs PyYAML 6.0.2 via pip install --quiet --no-deps pyyaml==6.0.2 without --require-hashes. The existing post-code.sh and post-fix.sh use pinned versions with SHA256 checksums for all binary downloads (gitleaks, lychee, uv). This departs from the project's established supply-chain hygiene. The script runs on the GHA runner (not in the sandbox) with access to PUSH_TOKEN.
    Remediation: Pin PyYAML with --require-hashes and the known SHA256 of the wheel, pre-install in the runner image, or use stdlib-only parsing.

  • [missing-authorization] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — This PR adds ~595 lines across 8 files with no linked issue. The change introduces a registry-based pre-commit tool installation system — a non-trivial infrastructure change that would benefit from an issue documenting the problem, design decision, and maintainer approval.
    Remediation: Link to an existing issue or create one documenting the motivation and design.

Low

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The existing post-code.sh hard-fails (exit 1) on unsupported architectures for lychee. The new system silently skips with a ::warning::. Practical risk is negligible since GHA runners are exclusively x86_64 or aarch64, but the behavioral change should be intentional.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:56 — Duplicate (repo, hook_id) keys in the registry silently overwrite earlier entries via repo_hook_map[key] = tool. Not currently triggered but a latent bug if the registry grows.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check (tool --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) could match a library version triple in the output instead of the tool's own version, causing a spurious "already installed" skip.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The gitleaks entry with skip_install: true expands the registry's responsibility beyond install mapping to include hook exemption tracking. Consider clarifying the registry's single responsibility.

Info

  • [injection-vuln] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:64 — Warning sanitization handles :: sequences and newline-splitting but does not strip ANSI escape sequences. Attack surface is limited since data originates from the target repo's own .pre-commit-config.yaml.

  • [command-injection] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:207NAME and VERSION are passed to apt-get/pip/npm without shell-safe character validation. Currently sourced from the trusted committed registry, so risk is negligible.

  • [design-trajectory] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The YAML registry format may not scale gracefully past ~10-15 tools due to per-tool version pins, per-arch checksums, and URL template maintenance.

Previous run (3)

Review

Findings

Medium

  • [injection-vuln] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:65 — The warnings loop sanitizes :: sequences (line 64: w="${w//::/ }") but does not sanitize literal newlines. Warning text from resolve-precommit-tools.py interpolates hook_id and entry values from the target repo's .pre-commit-config.yaml (untrusted). A malicious .pre-commit-config.yaml could embed a literal newline in a hook_id. The value flows through json.dumps (escapes \n) then jq -r (unescapes back to literal newline), producing a multi-line echo that injects arbitrary GHA workflow commands (e.g., ::add-mask::).
    Remediation: Strip newlines and carriage returns in addition to ::: w="${w//$'\n'/ }"; w="${w//$'\r'/ }"

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-code.sh:311 — When resolve-precommit-tools.py fails, the || branch prints a warning but stdout may contain partial/corrupt JSON written to $MANIFEST before the failure. The downstream jq -e check catches corruption and safely skips install, but the feature silently does nothing — tools that should have been installed are not. Same pattern in post-fix.sh, pre-code.sh, pre-fix.sh.
    Remediation: Truncate the manifest on failure: python3 ... > "${MANIFEST}" || { echo '::warning::...'; : > "${MANIFEST}"; }

  • [architectural-fit] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:22 — The resolver auto-installs PyYAML 6.0.2 via pip if not present, without --require-hashes. This introduces a new pattern (auto-installing runtime dependencies) that departs from the project's established supply-chain hygiene (pinned binary downloads with SHA256 verification).
    Remediation: Consider pre-installing PyYAML in the runner image, or add --hash to the pip install.

  • [comment-reference] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:10 — Multiple file comments reference resolve-precommit-tools.sh but the actual script is resolve-precommit-tools.py. Appears in install-precommit-tools.sh (lines 10, 24) and precommit-tools.yaml (lines 5, 32).
    Remediation: Update all references from .sh to .py.

  • [missing-authorization] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — This PR introduces a registry-based system (577 additions across 8 files) with no linked issue. Non-trivial changes require explicit authorization.
    Remediation: Link to an existing issue or create one.

  • [scope-tier-mismatch] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — PR title claims feat(scaffold) but the change primarily replaces existing hardcoded tool installation with a declarative registry system. Per COMMITS.md, restructuring internals is refactor:, not feat:. GoReleaser uses commit prefixes for release notes.
    Remediation: Consider refactor(scaffold): if the primary change is restructuring install logic.

Low

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:171 — The extra_binaries iteration could process empty strings from jq output. Add a guard: [ -z "${extra}" ] && continue.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:56 — Duplicate (repo, hook_id) keys in the registry would silently overwrite earlier entries. Not currently triggered but a latent bug.

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The old code hard-failed (exit 1) on unsupported architecture for lychee. The new system silently skips with a warning.

  • [race-condition] internal/scaffold/fullsend-repo/scripts/pre-code.sh:499 — Pre-scripts may run before the target repo checkout, causing the tool-install block to be silently skipped. Post-scripts provide the authoritative fallback.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The gitleaks entry with skip_install: true expands the registry's responsibility to include recognition of externally-managed tools.

  • [variable-naming] internal/scaffold/fullsend-repo/scripts/post-code.sh:287SCRIPT_DIR_POST deviates from the established SCRIPT_DIR pattern.

  • [naming-alignment] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py — New files consistently use precommit (no hyphen) while upstream uses pre-commit.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check could match a library version triple instead of the tool version.

  • [docstring-formatting] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:3 — Python docstring lacks a blank line after the summary line (PEP 257).

Info

  • [documentation-gap] internal/scaffold/scaffold.go — No ADR or architecture doc update for the new registry-driven installation system.

  • [design-trajectory] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The registry format may not scale gracefully past ~10-15 tools.

  • [command-injection] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:199NAME passed to apt-get/pip/npm is not validated for safe characters. Currently comes from the trusted registry.

Previous run (4)

Review

Findings

Low

  • [correctness] tools/precommit-tools.yaml:52 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install extracted the binary from the tarball root with no directory prefix (tar xzf ... -C "$HOME/.local/bin" lychee). If the lychee tarball places the binary at the root (no subdirectory), the install script falls back to find to locate it, producing a spurious ::warning::Binary not found at expected path message on every install. Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.

  • [correctness] scripts/resolve-precommit-tools.sh:77 — PyYAML is installed with an unpinned version floor (pip install "pyyaml>=6.0"). Every other dependency in this PR uses pinned versions with SHA256 checksums for supply-chain safety. While a compromised PyYAML could only influence tool selection (not the tool binaries themselves, which are checksum-verified), pinning to a specific version (e.g., pyyaml==6.0.2) would be consistent with the supply-chain posture established by the rest of this change.

Previous run (5)

Review

Findings

Medium

  • [correctness] post-code.sh, post-fix.sh, pre-code.sh, pre-fix.sh (all 4 call sites) — All callers redirect stderr into the manifest file with bash "${RESOLVE_SCRIPT}" ... > "${MANIFEST}" 2>&1 || true. The resolve script writes diagnostic messages to stderr (e.g., ::error:: and ::warning:: annotations, PyYAML install output). When any stderr output is present, it gets prepended to the JSON manifest, corrupting it. The downstream jq -e validation catches the corruption and safely skips the install, but the feature silently does nothing — tools that should have been installed are not.
    Remediation: Change 2>&1 to 2>/dev/null (if stderr diagnostics are disposable) or redirect stderr to a separate file and log it.

Low

  • [correctness] tools/precommit-tools.yaml:52 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install (tar xzf ... -C "/sandbox/.local/bin" lychee) extracts the binary from the tarball root with no directory prefix. If the lychee tarball places the binary at the root (no subdirectory), the install script falls back to find to locate it, producing a spurious ::warning::Binary not found at expected path message on every install. Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.
Previous run (6)

Review

Findings

Medium

  • [injection-vuln] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:65 — The warnings loop sanitizes :: sequences (line 64: w="${w//::/ }") but does not sanitize literal newlines. Warning text from resolve-precommit-tools.py interpolates hook_id and entry values from the target repo's .pre-commit-config.yaml (untrusted). A malicious .pre-commit-config.yaml could embed a literal newline in a hook_id. The value flows through json.dumps (escapes \n) then jq -r (unescapes back to literal newline), producing a multi-line echo that injects arbitrary GHA workflow commands (e.g., ::add-mask::).
    Remediation: Strip newlines and carriage returns in addition to ::: w="${w//$'\n'/ }"; w="${w//$'\r'/ }"

  • [error-handling] internal/scaffold/fullsend-repo/scripts/post-code.sh:311 — When resolve-precommit-tools.py fails, the || branch prints a warning but stdout may contain partial/corrupt JSON written to $MANIFEST before the failure. The downstream jq -e check catches corruption and safely skips install, but the feature silently does nothing — tools that should have been installed are not. Same pattern in post-fix.sh, pre-code.sh, pre-fix.sh.
    Remediation: Truncate the manifest on failure: python3 ... > "${MANIFEST}" || { echo '::warning::...'; : > "${MANIFEST}"; }

  • [architectural-fit] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:22 — The resolver auto-installs PyYAML 6.0.2 via pip if not present, without --require-hashes. This introduces a new pattern (auto-installing runtime dependencies) that departs from the project's established supply-chain hygiene (pinned binary downloads with SHA256 verification).
    Remediation: Consider pre-installing PyYAML in the runner image, or add --hash to the pip install.

  • [comment-reference] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:10 — Multiple file comments reference resolve-precommit-tools.sh but the actual script is resolve-precommit-tools.py. Appears in install-precommit-tools.sh (lines 10, 24) and precommit-tools.yaml (lines 5, 32).
    Remediation: Update all references from .sh to .py.

  • [missing-authorization] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — This PR introduces a registry-based system (577 additions across 8 files) with no linked issue. Non-trivial changes require explicit authorization.
    Remediation: Link to an existing issue or create one.

  • [scope-tier-mismatch] PR feat(scaffold): auto-detect and install pre-commit tool dependencies #1055 — PR title claims feat(scaffold) but the change primarily replaces existing hardcoded tool installation with a declarative registry system. Per COMMITS.md, restructuring internals is refactor:, not feat:. GoReleaser uses commit prefixes for release notes.
    Remediation: Consider refactor(scaffold): if the primary change is restructuring install logic.

Low

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:171 — The extra_binaries iteration could process empty strings from jq output. Add a guard: [ -z "${extra}" ] && continue.

  • [logic-error] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:56 — Duplicate (repo, hook_id) keys in the registry would silently overwrite earlier entries. Not currently triggered but a latent bug.

  • [behavioral-regression] internal/scaffold/fullsend-repo/scripts/post-code.sh — The old code hard-failed (exit 1) on unsupported architecture for lychee. The new system silently skips with a warning.

  • [race-condition] internal/scaffold/fullsend-repo/scripts/pre-code.sh:499 — Pre-scripts may run before the target repo checkout, causing the tool-install block to be silently skipped. Post-scripts provide the authoritative fallback.

  • [scope-creep] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The gitleaks entry with skip_install: true expands the registry's responsibility to include recognition of externally-managed tools.

  • [variable-naming] internal/scaffold/fullsend-repo/scripts/post-code.sh:287SCRIPT_DIR_POST deviates from the established SCRIPT_DIR pattern.

  • [naming-alignment] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py — New files consistently use precommit (no hyphen) while upstream uses pre-commit.

  • [edge-case] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:93 — Version check could match a library version triple instead of the tool version.

  • [docstring-formatting] internal/scaffold/fullsend-repo/scripts/resolve-precommit-tools.py:3 — Python docstring lacks a blank line after the summary line (PEP 257).

Info

  • [documentation-gap] internal/scaffold/scaffold.go — No ADR or architecture doc update for the new registry-driven installation system.

  • [design-trajectory] internal/scaffold/fullsend-repo/scripts/precommit-tools.yaml — The registry format may not scale gracefully past ~10-15 tools.

  • [command-injection] internal/scaffold/fullsend-repo/scripts/install-precommit-tools.sh:199NAME passed to apt-get/pip/npm is not validated for safe characters. Currently comes from the trusted registry.

Previous run (7)

Review

Findings

Low

  • [correctness] tools/precommit-tools.yaml:52 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install extracted the binary from the tarball root with no directory prefix (tar xzf ... -C "$HOME/.local/bin" lychee). If the lychee tarball places the binary at the root (no subdirectory), the install script falls back to find to locate it, producing a spurious ::warning::Binary not found at expected path message on every install. Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.

  • [correctness] scripts/resolve-precommit-tools.sh:77 — PyYAML is installed with an unpinned version floor (pip install "pyyaml>=6.0"). Every other dependency in this PR uses pinned versions with SHA256 checksums for supply-chain safety. While a compromised PyYAML could only influence tool selection (not the tool binaries themselves, which are checksum-verified), pinning to a specific version (e.g., pyyaml==6.0.2) would be consistent with the supply-chain posture established by the rest of this change.

Previous run (8)

Review

Findings

Medium

  • [correctness] post-code.sh, post-fix.sh, pre-code.sh, pre-fix.sh (all 4 call sites) — All callers redirect stderr into the manifest file with bash "${RESOLVE_SCRIPT}" ... > "${MANIFEST}" 2>&1 || true. The resolve script writes diagnostic messages to stderr (e.g., ::error:: and ::warning:: annotations, PyYAML install output). When any stderr output is present, it gets prepended to the JSON manifest, corrupting it. The downstream jq -e validation catches the corruption and safely skips the install, but the feature silently does nothing — tools that should have been installed are not.
    Remediation: Change 2>&1 to 2>/dev/null (if stderr diagnostics are disposable) or redirect stderr to a separate file and log it.

Low

  • [correctness] tools/precommit-tools.yaml:52 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install (tar xzf ... -C "/sandbox/.local/bin" lychee) extracts the binary from the tarball root with no directory prefix. If the lychee tarball places the binary at the root (no subdirectory), the install script falls back to find to locate it, producing a spurious ::warning::Binary not found at expected path message on every install. Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.


# ── gitleaks (secret scanning) ────────────────────────────────────
# Post-scripts install gitleaks independently as a security gate.
# This entry exists only so the resolver recognizes gitleaks hooks

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] correctness

The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install extracted the binary from the tarball root. If the tarball has no subdirectory, the find fallback triggers with a spurious warning on every install.

Suggested fix: Verify the lychee tarball structure and remove strip_prefix if the binary is at the root.

# Also build entry-based lookup for local hooks matched by entry content
repo_hook_map = {}
entry_match_map = {}
for tool in registry_tools:

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] correctness

PyYAML is installed with an unpinned version floor (pyyaml>=6.0). Every other dependency in this PR uses pinned versions with SHA256 checksums. A compromised PyYAML could influence tool selection, though tool binaries are checksum-verified.

Suggested fix: Pin PyYAML to a specific version (e.g., pyyaml==6.0.2) for consistency with the supply-chain posture.

@fullsend-ai-review

Copy link
Copy Markdown

Review follow-ups

Created follow-up issues for actionable non-blocking review findings:

  • #1056 — The strip_prefix for lychee is set to "lychee-{triple}", but the original hardcoded install extracted the binary from the tarball root. If the tarball has no subdirectory, the find fallback triggers with a spurious warning on every install.
  • #1057 — PyYAML is installed with an unpinned version floor (pyyaml>=6.0). Every other dependency in this PR uses pinned versions with SHA256 checksums. A compromised PyYAML could influence tool selection, though tool binaries are checksum-verified.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't this be top level? Now it will be distributed/

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good question — moved it from tools/ to scripts/ in a follow-up push but forgot to reply here.

The registry lives under scripts/ because resolve-precommit-tools.py looks it up relative to its own directory (os.path.join(script_dir, "precommit-tools.yaml")), so co-locating them keeps the lookup simple with no extra path configuration.

Also, scripts/ is a layered directory in scaffold.go — it gets distributed at runtime via reusable workflows, not installed into .fullsend repos. Putting it at the top level would mean it gets scaffolded into every org's config repo, which isn't the intent.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But having it as a layered directory does the same as being installed into repositories from the practical point of view, no? The script will install OUR tools for all repositories using fullsend. We need to move this to toplevel, so the script will get layered, but the definition of OUR tools won't. Users of fullsend can take advantage of this creating its own precommit-tools.yaml at the root level.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I see what you mean — since scripts/ is layered, the registry does travel with the scripts at runtime. But I think that's the right behavior for this PR's scope, and here's why:

What this PR replaces: The hardcoded LYCHEE_VERSION/LYCHEE_SHA256_AMD64/UV_VERSION/UV_SHA256 blocks that were previously inline in post-code.sh and post-fix.sh. Those were also distributed via the same layered scripts/ path. The registry is the same data externalized into a structured file — it doesn't change what gets distributed, just how it's organized.

What the registry is: Fullsend's knowledge of which pre-commit hook repos need which system tools. It's infrastructure knowledge, not user configuration. Users don't need to know (or care) that lychee needs a specific binary download with a specific checksum — that's fullsend's job.

User-provided overrides are #1270 territory. The resolver already takes registry_path as a parameter (not hardcoded in resolve()), so adding a merge step later — load fullsend's built-in registry, overlay a user-provided precommit-tools.yaml from the target repo — is straightforward. But that's a different feature with different requirements (merge semantics, conflict resolution, validation).

This PR needs to land so the follow-up work can proceed — #1270 (registry expansion), #836 (shared tool install logic), #850 (pre-flight checks), and #1056/#1057 (review findings) are all blocked on it. I'd rather not widen the scope further.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about shellcheck and actionlint? Those are in the file, but they weren't on post-code. They are currently compiled by golang

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good call — you're right that shellcheck and actionlint weren't in the old hardcoded blocks.

shellcheck: removed. Both shellcheck-py/shellcheck-py (language: python) and koalaman/shellcheck-precommit (language: docker_image) are self-managed by pre-commit — it installs shellcheck-py via pip/venv or pulls the Docker image. The apt-installed system shellcheck was wasted work and could cause version skew with the pip-bundled binary. Dropped both entries.

actionlint: kept with rationale comment. The hook uses language: golang, so pre-commit CAN compile it from source, but that takes ~2 minutes on GHA runners. The registry downloads the pre-built binary in ~3 seconds. It's a performance optimization, not a correctness fix. Added an explicit comment making this trade-off visible. If you'd rather let pre-commit handle it natively and accept the build time, I can drop it too.

Also added a comment to the registry header documenting when entries are appropriate (only for hooks pre-commit can't self-serve) and how to customize (place .pre-commit-tools.yaml in customized/scripts/ for full replacement). Additive merge (adding/suppressing individual entries without replacing the whole file) is follow-up territory (#1270).

@rh-hemartin rh-hemartin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hm.. I don't like this. It is mixing bash and Python in a weird way, create a Python file and use the bash to forward all the arguments if needed.

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had any activity in the last month. It will be closed in 2 weeks if no further activity occurs. Remove the stale label to reset the inactivity timer.

@github-actions github-actions Bot added the stale label Jun 18, 2026
@waynesun09
waynesun09 force-pushed the auto-precommit-tools branch from 39bd65a to 70151ea Compare June 18, 2026 13:50
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

E2E tests are running

Authorization passed for this commit. See the E2E Tests workflow for results.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 1:54 PM UTC · Ended 1:57 PM UTC
Commit: 4e21a60 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 2:01 PM UTC · Ended 2:05 PM UTC
Commit: 4e21a60 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 2:09 PM UTC · Ended 2:12 PM UTC
Commit: 4e21a60 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 18, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 2:15 PM UTC · Ended 2:19 PM UTC
Commit: 4e21a60 · View workflow run →

@codecov

codecov Bot commented Jun 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

ralphbean added a commit that referenced this pull request Jun 22, 2026
Add a pre-commit hook that runs `pinact run --fix=false --no-api` to
verify all GitHub Actions references use full-length commit SHAs. The
--no-api flag ensures the check is offline-only (syntactic SHA presence)
so it won't break when new action versions are released.

Also install pinact in the CI lint workflow so the hook passes there.

Depends-on: #1055 (auto-detect pre-commit tool dependencies)

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@waynesun09 waynesun09 added the ok-to-test Allow e2e CI to run after maintainer review (must be re-applied after each push) label Jun 22, 2026
ralphbean added a commit that referenced this pull request Jun 22, 2026
Add a pre-commit hook that runs `pinact run --fix=false --no-api` to
verify all GitHub Actions references use full-length commit SHAs. The
--no-api flag ensures the check is offline-only (syntactic SHA presence)
so it won't break when new action versions are released.

Also install pinact in the CI lint workflow so the hook passes there.

Depends-on: #1055 (auto-detect pre-commit tool dependencies)

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
ralphbean added a commit that referenced this pull request Jun 23, 2026
Add a pre-commit hook that runs `pinact run --fix=false --no-api` to
verify all GitHub Actions references use full-length commit SHAs. The
--no-api flag ensures the check is offline-only (syntactic SHA presence)
so it won't break when new action versions are released.

Also install pinact in the CI lint workflow so the hook passes there.

Depends-on: #1055 (auto-detect pre-commit tool dependencies)

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
ralphbean added a commit that referenced this pull request Jun 24, 2026
Add a pre-commit hook that runs `pinact run --fix=false --no-api` to
verify all GitHub Actions references use full-length commit SHAs. The
--no-api flag ensures the check is offline-only (syntactic SHA presence)
so it won't break when new action versions are released.

Also install pinact in the CI lint workflow so the hook passes there.

Depends-on: #1055 (auto-detect pre-commit tool dependencies)

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
ifireball pushed a commit to ifireball/fullsend that referenced this pull request Jun 24, 2026
Add a pre-commit hook that runs `pinact run --fix=false --no-api` to
verify all GitHub Actions references use full-length commit SHAs. The
--no-api flag ensures the check is offline-only (syntactic SHA presence)
so it won't break when new action versions are released.

Also install pinact in the CI lint workflow so the hook passes there.

Depends-on: fullsend-ai#1055 (auto-detect pre-commit tool dependencies)

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Ralph Bean <rbean@redhat.com>
@waynesun09
waynesun09 force-pushed the auto-precommit-tools branch from 831a6bb to 733f6a3 Compare June 24, 2026 14:30
Add a registry-based system for resolving and installing pre-commit
hook tool dependencies at runtime, replacing hardcoded tool installs
baked into OpenShell container images.

New files:
- tools/precommit-tools.yaml: registry mapping hook repos/IDs to
  system tools with pinned versions and SHA256 checksums
- scripts/resolve-precommit-tools.py: standalone Python resolver
  that parses .pre-commit-config.yaml against the registry
- scripts/resolve-precommit-tools.sh: bash wrapper ensuring PyYAML
  is available before invoking the Python resolver
- scripts/install-precommit-tools.sh: installs tools from the JSON
  manifest (binary/apt/pip/npm) with architecture detection

Modified pre/post scripts (pre-code, pre-fix, post-code, post-fix)
to call the resolver and installer instead of hardcoding tool
versions. Removes LYCHEE_VERSION/UV_VERSION constants from
post-code.sh and post-fix.sh.

Supply-chain hardening:
- Binary downloads use pinned versions + SHA256 checksums
- pip installs use --no-deps to prevent transitive dependency attacks
- npm installs use --ignore-scripts to prevent install-time RCE
- jq architecture lookups use --arg binding (not shell interpolation)
- PyYAML pinned to ==6.0.2
- Pre-scripts write to GITHUB_PATH for cross-step persistence

Closes #1270

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09
waynesun09 added this pull request to the merge queue Jun 25, 2026
Merged via the queue into main with commit 397e330 Jun 25, 2026
14 checks passed
@waynesun09
waynesun09 deleted the auto-precommit-tools branch June 25, 2026 13:29
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 25, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 1:33 PM UTC · Completed 1:40 PM UTC
Commit: 228b9e5 · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #1055 — feat(scaffold): auto-detect and install pre-commit tool dependencies

Timeline

  • May 16: PR opened by waynesun09 on branch auto-precommit-tools (human-authored). Review bot ran twice, posted initial findings and follow-up issues #1056, #1057.
  • May 18: Human reviewer (rh-hemartin) requested changes — registry file placement and bash/Python mixing concerns.
  • May 18 – Jun 18: PR went stale for one month. GitHub Actions bot marked it stale.
  • Jun 18: PR revived with force-pushes. Review bot ran 7 more times (5 cancelled, 2 succeeded). Human and author debated registry placement architecture.
  • Jun 22–24: Further revisions. Review bot's run 10 falsely marked PyYAML --require-hashes as resolved; run 11 self-corrected, flagging it as regressed.
  • Jun 25: Human approved and merged. One medium-severity finding (PyYAML --require-hashes) remained unresolved at merge.

Workflow Quality Assessment

Review quality: Good, with complementary human/bot coverage. The review bot excelled at implementation correctness and security (PyYAML supply chain, injection vulnerabilities, edge cases). The human reviewer focused on architectural design (file placement in layered directories, bash/Python separation, which tools belong in the registry). Neither fully substituted for the other.

Rework rate: Moderate. The PR went through 8 head SHAs, but much of this was driven by substantive design feedback from the human reviewer, not bot-driven churn.

Token cost: Elevated. 9 review runs (5 cancelled) represents significant waste. The 5 cancellations on SHA 4e21a60 across Jun 18–22 suggest the bot was re-triggered by events on a SHA it had already reviewed or was mid-review on.

Notable bot behavior: The bot demonstrated good self-correction by catching its own false "resolved" marking for PyYAML --require-hashes in a subsequent run. However, the false marking itself (claiming the code included hash verification when it didn't) represents a hallucination in resolution tracking.

Existing Issue Coverage

Most improvement opportunities identified are already tracked by open issues:

  • Cancelled/redundant review runs on force-pushed PRs: #902, #2116, #2111
  • Finding resolution tracking / regression detection: #1676, #1044, #956
  • Architectural/design-level review gaps: #1469, #2325
  • Human approval with unresolved medium+ findings: #2099
  • Validate findings against actual code state: #1306

Proposals

One novel improvement identified (see below). All other potential proposals were filtered as duplicates of existing open issues listed above.

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/harness Agent harness, config, and skills loading ok-to-test Allow e2e CI to run after maintainer review (must be re-applied after each push) requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants