Skip to content

fix(guard): scope the freshness gate to git commit, and fix its hint#164

Merged
CryptoJones merged 1 commit into
mainfrom
fix/git-freshness-hint-standalone-fetch
Jul 21, 2026
Merged

fix(guard): scope the freshness gate to git commit, and fix its hint#164
CryptoJones merged 1 commit into
mainfrom
fix/git-freshness-hint-standalone-fetch

Conversation

@CryptoJones

Copy link
Copy Markdown
Owner

What

Two related changes to the repository freshness control in guard.py.

1. Scope the freshness gate to git commit only (per CJ, 2026-07-20)

Previously every repo-sensitive action — edits, tests, pushes, review-reads —
demanded a same-turn git fetch. A commit is the moment a stale local base
actually gets recorded, so only it is gated now. Edits, tests, pushes, and reads
still require the git-rules-note consult; they no longer require a fetch.
repo-work-fresh-base now fires solely on a git commit (new _is_commit_action).

2. Fix the self-contradictory block message

The old hint told agents to chain the fetch onto the commit:

git -C "/repo" fetch --all --prune && git -C "/repo" commit -am "..."

That can never satisfy the check. _is_freshness_command requires every part
of the command to be a git read/fetch, so a command that also contains the commit
records nothing and the commit stays blocked. The message now instructs a
standalone fetch first, then the commit as a separate command, and calls
out that any non-git-read step (even && echo) disqualifies the fetch and that
freshness resets each turn.

Tests

  • Rewrote the freshness tests to probe with commits (non-commit repo work is now
    allowed after the rules-note consult).
  • Added test_freshness_gate_applies_only_to_commits.
  • Added a message regression test (no chained fetch→commit remedy; described
    behaviour holds).
  • ruff check . clean; 782 passed, 1 skipped.

Version

Bumped to 4.2.2; both changes recorded in CHANGELOG.md.

Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/

Two related changes to the repository freshness control.

1. Narrow the freshness demand to `git commit` only (CJ, 2026-07-20).
   Previously every repo-sensitive action — edits, tests, pushes, and
   review-reads — required a same-turn fetch. A commit is the moment a
   stale local base is actually recorded, so only it is gated now.
   Edits, tests, pushes, and reads still require the git-rules consult;
   they no longer require a fetch. `repo-work-fresh-base` fires solely on
   a `git commit` (new helper `_is_commit_action`).

2. Fix the self-contradictory block message. Its worked example
   (`git fetch --all --prune && git commit …`) could never satisfy the
   check: a command that also contains the commit is not a pure freshness
   command, so `_is_freshness_command` returns False, nothing is recorded,
   and the commit stays blocked. The message now tells the agent to run a
   standalone literal-path fetch as its own command first, then commit as
   a separate command, and spells out that any non-git-read step (even
   `&& echo`) disqualifies the fetch and that freshness resets each turn.

Tests: rewrite the freshness tests to probe with commits (non-commit repo
work is now allowed after the rules-note consult), add
test_freshness_gate_applies_only_to_commits, and assert the message no
longer offers a chained fetch->commit remedy. ruff clean; 782 passed.
Bump to 4.2.2 and record both changes in CHANGELOG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Changed

    • Repository freshness checks now apply only when creating a Git commit.
    • Other repository actions can proceed without a same-turn fetch.
  • Bug Fixes

    • Updated freshness guidance with accurate instructions: run a standalone fetch, then commit separately.
    • Clarified that chained commands do not satisfy the freshness requirement and freshness resets each turn.
  • Release

    • Updated the package version to 4.2.2.

Walkthrough

Version 4.2.2 narrows repository freshness enforcement to git commit actions, updates the freshness guidance, adds regression coverage, and records the release changes.

Changes

Git freshness enforcement

Layer / File(s) Summary
Commit-only freshness enforcement
src/omind/guard.py
Freshness guidance now requires a standalone literal-path fetch followed by a separate commit, while enforcement detects commit actions including git -C and git -c forms.
Freshness behavior validation
tests/test_guard.py
Tests verify non-commit actions, stale commit blocking, standalone fetch behavior, chained-command rejection, message examples, and git -C handling.
4.2.2 release metadata
CHANGELOG.md, pyproject.toml, src/omind/__init__.py
Release notes and package version declarations are updated to 4.2.2.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: scoping freshness to git commit and fixing the hint.
Description check ✅ Passed The description directly matches the changeset and explains the freshness-gate and hint updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/git-freshness-hint-standalone-fetch

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@CHANGELOG.md`:
- Around line 12-13: Add blank lines immediately after the `### Changed` and
`### Fixed` headings in `CHANGELOG.md`, before their respective list content, to
satisfy markdownlint MD022.

In `@tests/test_guard.py`:
- Around line 164-181: Make the freshness assertions independent of the checkout
remote by creating an isolated temporary Git repository with a fake remote in
tests/test_guard.py lines 164-181 and 993-1015, then run every fetch and commit
command against it using git -C <repo>. Preserve the existing chained-fetch and
standalone-fetch assertions while ensuring both scenarios exercise a
remote-backed repository.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ef2ec6b6-223f-4979-8f6f-b8f36c81a1a6

📥 Commits

Reviewing files that changed from the base of the PR and between 196ca8c and 960ef3f.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • CHANGELOG.md
  • pyproject.toml
  • src/omind/__init__.py
  • src/omind/guard.py
  • tests/test_guard.py
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: test (windows-latest, 3.10)
  • GitHub Check: test (windows-latest, 3.14)
  • GitHub Check: test (macos-latest, 3.10)
🧰 Additional context used
🪛 ast-grep (0.44.1)
src/omind/guard.py

[warning] 885-885: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.compile(rf"(?:^|[;&|\n(]\s*)git[ \t]+{_GIT_GLOBAL_OPTS}commit\b")
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

tests/test_guard.py

[error] 277-280: Command coming from incoming request
Context: subprocess.run(
["git", "-C", str(repo), "remote", "add", "origin", "https://x.invalid/y.git"],
check=True,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🪛 markdownlint-cli2 (0.23.0)
CHANGELOG.md

[warning] 12-12: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 20-20: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🔇 Additional comments (4)
pyproject.toml (1)

3-3: LGTM!

src/omind/__init__.py (1)

5-5: LGTM!

src/omind/guard.py (1)

67-84: LGTM!

Also applies to: 884-901, 1096-1106

tests/test_guard.py (1)

10-10: LGTM!

Also applies to: 248-300, 1067-1076

Comment thread CHANGELOG.md
Comment on lines +12 to +13
### Changed
- **Narrowed the repository freshness gate to `git commit` only.** Previously any

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add blank lines after the release subsection headings.

Insert a blank line after ### Changed and ### Fixed so the changelog passes markdownlint MD022.

Also applies to: 20-21

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 12-12: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 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 `@CHANGELOG.md` around lines 12 - 13, Add blank lines immediately after the
`### Changed` and `### Fixed` headings in `CHANGELOG.md`, before their
respective list content, to satisfy markdownlint MD022.

Source: Linters/SAST tools

Comment thread tests/test_guard.py
Comment on lines +164 to +181
# A commit, however, still demands freshness.
blocked = guard.decide({"tool": "Bash", "command": "git commit -am x", "session": "repo"})
assert not blocked.allow
assert blocked.rule_id == "repo-work-fresh-base"

compound = guard.decide(
{"tool": "Bash", "command": "git fetch --all --prune && pytest", "session": "repo"}
)
# A fetch chained with the commit is NOT a pure freshness command, so it
# records nothing and the commit stays blocked.
compound_cmd = "git fetch --all --prune && git commit -am x"
compound = guard.decide({"tool": "Bash", "command": compound_cmd, "session": "repo"})
assert not compound.allow
assert compound.rule_id == "repo-work-fresh-base"

# A standalone fetch establishes freshness for the separate next commit.
fresh = guard.decide(
{"tool": "Bash", "command": "git fetch --all --prune", "session": "repo"}
)
assert fresh.allow
assert guard.decide({"tool": "Bash", "command": "pytest", "session": "repo"}).allow
assert guard.decide({"tool": "Bash", "command": "git commit -am x", "session": "repo"}).allow

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make freshness assertions independent of the checkout remote.

decide() only blocks stale commits for repositories with configured remotes. These commands target the runner’s CWD, so source-archive or no-remote runs will allow the commits and invalidate the assertions.

  • tests/test_guard.py#L164-L181: create a temporary Git repository with a fake remote and use git -C <repo> for each command.
  • tests/test_guard.py#L993-L1015: likewise use an isolated remote-backed repository for the chained and standalone-fetch scenarios.
📍 Affects 1 file
  • tests/test_guard.py#L164-L181 (this comment)
  • tests/test_guard.py#L993-L1015
🤖 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 `@tests/test_guard.py` around lines 164 - 181, Make the freshness assertions
independent of the checkout remote by creating an isolated temporary Git
repository with a fake remote in tests/test_guard.py lines 164-181 and 993-1015,
then run every fetch and commit command against it using git -C <repo>. Preserve
the existing chained-fetch and standalone-fetch assertions while ensuring both
scenarios exercise a remote-backed repository.

@CryptoJones
CryptoJones merged commit 960ef3f into main Jul 21, 2026
16 checks passed
@CryptoJones
CryptoJones deleted the fix/git-freshness-hint-standalone-fetch branch July 21, 2026 00:14
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