Skip to content

fix(auto-fix): harden sentinel-guarded artifact rehydration#45

Merged
khaliqgant merged 2 commits into
mainfrom
fix/auto-fix-sentinel-guarded-rehydration
May 5, 2026
Merged

fix(auto-fix): harden sentinel-guarded artifact rehydration#45
khaliqgant merged 2 commits into
mainfrom
fix/auto-fix-sentinel-guarded-rehydration

Conversation

@khaliqgant

Copy link
Copy Markdown
Member

Summary

Closes the auto-fix gap that left a sage workflow stuck on INVALID_ARTIFACT at final-review-pass-gate for all 7 retries (the recovery on https://github.com/AgentWorkforce/sage/pull/199 had to be done by hand).

Deterministic gates that lazily seed an artifact via if [ ! -f '<path>' ]; then cat > '<path>' <<'EOF' ... <marker> EOF fi followed later by tail -n 1 '<path>' | tr -d '[:space:]' | grep -E '^<marker>$' were unrecoverable when a leftover artifact from a prior run had trailing junk on its last line: the existence guard short-circuited, the heredoc never re-ran, and the auto-fix loop replayed the same guaranteed-to-fail command up to maxAttempts. None of the existing deterministic repairs (repairAgentStepTimeouts, missingFileRepair*, repairOutputContainsEchoMismatches, repairUnknownStepTemplateRefs, repairBareGitDiffManifestGates) inspect artifact content, and workforce-persona-repairer rewrites workflow definitions, not artifact bodies — so the loop made no progress.

Fix

repairWorkflowDeterministically now detects this gate shape and rewrites the existence-only guard into a content-aware guard: if [ ! -f '<path>' ] || ! tail -n 1 '<path>' | tr -d '[:space:]' | grep -qE '^<marker>$'; then — which lets the next attempt regenerate the corrupt artifact and clear the gate.

The repair only fires when a matching tail -n 1 '<path>' | ... grep '^<marker>$' check exists elsewhere in the same workflow source. Unrelated if [ ! -f ] seed blocks (e.g. one-shot README seeders) are left alone; a skip-test in the suite asserts this.

End-to-end smoke

With a corrupted leftover artifact whose last line is the heredoc-bleed garbage seen in the original sage failure:

  • Old guard: exit 1 (the original bug).
  • Hardened guard: regenerates the file, gate passes.

Test plan

  • npx tsc --noEmit clean
  • npx vitest run src/local/auto-fix-loop.test.ts — 15/15 (13 existing + 2 new)
  • npx vitest run — 826/826 across 43 files
  • Manual bash smoke comparing old vs hardened guard against a corrupted artifact

Out of scope (follow-up)

The corruption itself — a newline between EOF and the trailing tail-check getting joined to && somewhere in the pipeline — is a separate bug, almost certainly in command-string assembly when a heredoc-containing multi-line shell command is flattened. This PR only closes the recovery gap; happy to file a follow-up once the joiner is identified.

🤖 Generated with Claude Code

Deterministic gates that lazily seed an artifact via
`if [ ! -f '<path>' ]; then cat > '<path>' <<'EOF' ... <marker> EOF fi`
followed later by `tail -n 1 '<path>' | tr -d '[:space:]' | grep -E '^<marker>$'`
were unrecoverable when a leftover artifact from a prior run had trailing
junk on its last line: the `if [ ! -f ]` guard short-circuited, the
heredoc never re-ran, and the auto-fix loop replayed the same
guaranteed-to-fail command up to maxAttempts.

`repairWorkflowDeterministically` now detects this shape and rewrites
the guard to also re-rehydrate when the sentinel check fails:

    if [ ! -f '<path>' ] || ! tail -n 1 '<path>' \
        | tr -d '[:space:]' | grep -qE '^<marker>$'; then
      cat > '<path>' <<'EOF'
      ...
    EOF
    fi

The repair only fires when a matching `tail -n 1 ... grep '^<marker>$'`
check exists elsewhere in the same workflow source, so unrelated
`if [ ! -f ]` seed blocks are left alone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 16d9ba83-1f73-4d5f-9186-355dd7d1bf20

📥 Commits

Reviewing files that changed from the base of the PR and between 785f255 and 8091771.

📒 Files selected for processing (1)
  • src/local/auto-fix-loop.ts

📝 Walkthrough

Walkthrough

Added a deterministic repair pass that detects workflow shell blocks which rehydrate files with embedded sentinel markers and hardens them by inserting a tail/grep sentinel check; also added tests and helpers validating applied and non-applicable repair cases.

Changes

Sentinel-Guarded Rehydration Hardening

Layer / File(s) Summary
Detection & Rewrite Logic
src/local/auto-fix-loop.ts (lines 527–545)
New repairSentinelGuardedRehydration(content) locates if [ ! -f 'path' ]; then cat > 'path' <<'EOF' ... marker EOF fi blocks and rewrites them to require the file's last line to equal the embedded marker using tail -n 1 'path' | tr -d '[:space:]' | grep -qE '^marker$'.
Guard De-duplication & Recording
src/local/auto-fix-loop.ts (same area)
Function skips rewriting when an existing tail/grep sentinel for the same path+marker is present and deduplicates recorded change messages.
Integration into Deterministic Repairs
src/local/auto-fix-loop.ts (lines 381–389)
repairWorkflowDeterministically now invokes repairSentinelGuardedRehydration after other deterministic repairs and appends any produced change descriptions to the deterministic repair changes list.
Tests & Helpers
src/local/auto-fix-loop.test.ts (lines 288–321, 1037–1138)
Added two Vitest cases exercising hardened sentinel rehydration and the non-applicable path. Added helper generators for workflow contents (with and without tail check) and corresponding failure evidence used by the tests.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop through scripts with markers bright,
Fixing rehydration by tailing the night.
A sentinel check, neat and spry,
Keeps artifacts honest as I pass by.
Cheers for repairs — a little rabbit sigh.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: hardening sentinel-guarded artifact rehydration in the auto-fix system.
Description check ✅ Passed The description is directly related to the changeset, providing context about the problem, the solution, test coverage, and known limitations.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/auto-fix-sentinel-guarded-rehydration

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
src/local/auto-fix-loop.ts (1)

547-549: ⚡ Quick win

Remove duplicate helper; reuse existing escapeRegExp at line 842.

This escapeRegex function is identical to the existing escapeRegExp function defined later in this file. Use the existing function to avoid duplication.

♻️ Suggested fix
-function escapeRegex(value: string): string {
-  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
-}

Then update the call site at line 536:

     const sentinelCheckPattern = new RegExp(
-      `tail -n 1 '${escapeRegex(path)}'[^\\n]*\\| grep[^\\n]*'\\^${escapeRegex(marker)}\\$'`,
+      `tail -n 1 '${escapeRegExp(path)}'[^\\n]*\\| grep[^\\n]*'\\^${escapeRegExp(marker)}\\$'`,
     );
🤖 Prompt for 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.

In `@src/local/auto-fix-loop.ts` around lines 547 - 549, Remove the duplicate
escapeRegex helper and reuse the existing escapeRegExp function: delete the
escapeRegex function declaration and update any call-sites that currently call
escapeRegex (e.g., the call near where it was used) to call escapeRegExp
instead, ensuring imports/exports (if any) and TypeScript types remain
consistent; run a quick search in the file for "escapeRegex(" to replace all
occurrences and keep only the single escapeRegExp implementation.
🤖 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.

Nitpick comments:
In `@src/local/auto-fix-loop.ts`:
- Around line 547-549: Remove the duplicate escapeRegex helper and reuse the
existing escapeRegExp function: delete the escapeRegex function declaration and
update any call-sites that currently call escapeRegex (e.g., the call near where
it was used) to call escapeRegExp instead, ensuring imports/exports (if any) and
TypeScript types remain consistent; run a quick search in the file for
"escapeRegex(" to replace all occurrences and keep only the single escapeRegExp
implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dad0ec9a-ea5a-49f7-9b39-a21f25a359fc

📥 Commits

Reviewing files that changed from the base of the PR and between 829bed8 and 785f255.

📒 Files selected for processing (2)
  • src/local/auto-fix-loop.test.ts
  • src/local/auto-fix-loop.ts

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 785f25552b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/local/auto-fix-loop.ts Outdated

const next = content.replace(guardPattern, (match, path: string, indent: string, body: string, marker: string) => {
const sentinelCheckPattern = new RegExp(
`tail -n 1 '${escapeRegex(path)}'[^\\n]*\\| grep[^\\n]*'\\^${escapeRegex(marker)}\\$'`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept double-quoted grep markers in sentinel guard repair

repairSentinelGuardedRehydration only recognizes later sentinel checks where the regex is single-quoted ('^…$'), because sentinelCheckPattern hardcodes that quote style. If a workflow uses the equally valid grep -Eq "^MARKER$" form, the if [ ! -f ... ]; then cat ... fi block is left unchanged, so retries can still loop on a corrupt leftover artifact instead of regenerating it. This makes the new recovery logic miss a common shell variant and leaves the original failure mode in place for those workflows.

Useful? React with 👍 / 👎.

@khaliqgant khaliqgant merged commit 64b191a into main May 5, 2026
1 check passed
@khaliqgant khaliqgant deleted the fix/auto-fix-sentinel-guarded-rehydration branch May 5, 2026 11:09
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.

1 participant