Skip to content

feat: git sync engine (Plan B of issue #16)#134

Merged
michaelmcnees merged 18 commits into
mainfrom
feat/git-sync-engine
May 30, 2026
Merged

feat: git sync engine (Plan B of issue #16)#134
michaelmcnees merged 18 commits into
mainfrom
feat/git-sync-engine

Conversation

@michaelmcnees

@michaelmcnees michaelmcnees commented Apr 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Plan B of 3 for issue #16. Stacked on #133 (Plan A foundation). Ships the auto-apply sync engine end-to-end.

  • New internal/repo/sync package: Driver interface with NoopDriver (tests / CI-driven deployments) and GoGitDriver (standalone, via github.com/go-git/go-git/v5). Discover walks YAML files and hashes them. SyncRepo orchestrates pull → discover → parse + save per file. Reconcile materializes git_sync.repos config into git_repos rows at startup. Poller runs one goroutine per enabled auto_apply: true repo.
  • Store.UpdateSyncState writes last_sync_sha, last_sync_at, last_sync_error after each sync attempt.
  • mantle repos sync <name> CLI with --from-dir flag for CI / test scenarios (uses NoopDriver).
  • Startup wiring in mantle serve: reconciles the git_sync.repos config block, then launches the poller goroutine tied to the signal-aware context.
  • Credential resolver: NewAuthResolver looks up git-type credentials and converts HTTPS tokens into http.BasicAuth for go-git. SSH keys return an explicit "not supported yet" error (ships in Plan C).
  • Audit events: git.sync.started, git.sync.completed, git.sync.failed, git.sync.validation_failed (parse errors), git.sync.apply_failed (workflow.Save errors).

What operators can do today (A + B together)

  • Register repos via mantle repos add or git_sync.repos: in mantle.yaml
  • Run mantle serve — the poller syncs each auto_apply: true repo every poll_interval, applying changed workflow YAML as new immutable versions
  • Force a sync with mantle repos sync <name>
  • Observe sync state via mantle repos status <name> — SHA, timestamp, truncated error summary
  • Audit log captures every sync lifecycle event for compliance review

Pre-push review fixes (commit 2eca439)

Four reviewer agents ran before push (Technical Writer, Product Manager, Legal Compliance, Reality Checker). Consolidated fixes:

  • Drop "sync engine ships in a later milestone" text from mantle repos help now that it's actually here
  • Split ActionGitSyncValidationFailed (parse errors) from ActionGitSyncApplyFailed (save errors) — one audit consumer, two root-cause categories
  • Wire NewAuthResolver into both the poller and the repos sync CLI so private repos authenticate instead of silently failing
  • Add auto_apply: false hint to mantle repos status output so operators know the background poller skips those repos
  • Document that Poller.Run snapshots the repo list at startup (live reconfig ships in Plan C)
  • Expand --from-dir flag help

Plan C TODOs (tracked, not blocking)

  • Clean up the <artifact>/git/<repo-id>/ working tree when mantle repos remove is called (SOC 2 CC6.5 disposal)
  • Sanitize err.Error() before placing it in git.sync.failed audit metadata in case go-git echoes the remote URL
  • Add a config-guide note that storage.path should be explicit in production (the os.TempDir() fallback has umask-dependent permissions)
  • SSH key credential support
  • Live reconfig of the poller on git_repos changes
  • mantle repos update CLI (store has Update; only the CLI surface is missing)
  • Prune behavior (needs a schema addition)
  • Manual auto_apply: false plan/apply flow, webhook receiver, REST API, metrics

Test plan

  • go test ./... in packages/engine — all green
  • go test ./internal/repo/sync/ — 14 tests (Driver, Discover, SyncRepo, Reconcile, Poller, NewAuthResolver)
  • go test ./internal/repo/ — 15 tests (Plan A CRUD + UpdateSyncState)
  • go test ./internal/cli/ — includes TestReposSync_UsesNoopDriverWithFromDir
  • go build ./cmd/mantle — clean
  • Manual: mantle serve against a repo with git_sync.repos: in mantle.yaml, watch last_sync_at populate on each poll tick
  • Manual: mantle repos sync <name> against a private GitHub repo with a PAT credential

Closes part of #16. Plan C follows.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added mantle repos sync <name> command to trigger immediate repository synchronization
    • Added automatic background polling during mantle serve for repositories with auto-apply enabled
    • Repository sync state now tracks last sync SHA, timestamp, and error details
    • GitOps configuration reconciliation automatically manages registered repositories

Review Change Stack

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@michaelmcnees, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 45 minutes and 4 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5f3bd80-0705-4e25-aed7-425e7b3be231

📥 Commits

Reviewing files that changed from the base of the PR and between 37f6147 and 8883201.

📒 Files selected for processing (3)
  • .github/workflows/engine-ci.yml
  • packages/engine/internal/cli/repos_test.go
  • packages/engine/internal/repo/sync/discover.go
📝 Walkthrough

Walkthrough

This pull request introduces a complete GitOps repository sync engine that enables automatic and manual synchronization of workflow repositories. It implements driver abstractions for flexible repository pulling, integrates background polling for auto-apply repos, reconciles configuration into the database, and provides CLI commands plus daemon startup integration.

Changes

GitOps Repository Sync Engine

Layer / File(s) Summary
Sync driver contract and implementations
packages/engine/internal/repo/sync/driver.go, packages/engine/internal/repo/sync/noop_driver.go, packages/engine/internal/repo/sync/noop_driver_test.go, packages/engine/internal/repo/sync/gogit_driver.go, packages/engine/internal/repo/sync/gogit_driver_test.go
Driver interface defines Pull(ctx, *repo.Repo) (PullResult, error) contract. NoopDriver deterministically returns a configured path and SHA for testing. GoGitDriver clones/fetches/resets git repos into a base path with optional auth resolver and defaults to main branch; includes helpers for shallow clones and error cleanup.
Git authentication and workflow discovery
packages/engine/internal/repo/sync/auth.go, packages/engine/internal/repo/sync/auth_test.go, packages/engine/internal/repo/sync/discover.go, packages/engine/internal/repo/sync/discover_test.go
NewAuthResolver converts a secret.Resolver into a go-git auth callback, enforcing token-only (HTTP BasicAuth) authentication and rejecting SSH keys. Discover recursively walks a directory, collects .yaml/.yml files with SHA-256 hashes, and supports subpath filtering for workflows.
Sync engine orchestration and state persistence
packages/engine/internal/repo/sync/engine.go, packages/engine/internal/repo/sync/engine_test.go, packages/engine/internal/repo/store.go, packages/engine/internal/repo/store_test.go
SyncRepo orchestrates the full sync: emits audit start event, pulls repo via driver, discovers/parses/saves workflows, accumulates applied/unchanged/failure counts, updates sync state, and emits completion event. Store.UpdateSyncState persists LastSyncSHA, LastSyncAt, and clears LastSyncError on success. Integration tests validate applying files, detecting unchanged files, and handling parse failures without aborting.
Background polling and configuration reconciliation
packages/engine/internal/repo/sync/poller.go, packages/engine/internal/repo/sync/poller_test.go, packages/engine/internal/repo/sync/reconcile.go, packages/engine/internal/repo/sync/reconcile_test.go
Poller.Run(ctx) snapshots enabled auto-apply repos, starts one ticker goroutine per repo enforcing a MinInterval floor, calls SyncRepo on each tick, and optionally invokes OnSync callback. Reconcile syncs config entries with database state, creating missing repos with defaults (branch main, path /, interval 60s) and updating existing entries while preserving Enabled flag.
CLI repos sync command and serve daemon integration
packages/engine/internal/cli/repos.go, packages/engine/internal/cli/repos_test.go, packages/engine/internal/cli/serve.go
mantle repos sync <name> command supports --from-dir (using NoopDriver for pre-cloned dirs) or git cloning/pulling with optional encryption-based secret resolver. Updated repos status shows explanatory message when Auto-Apply: false. serve startup now reconciles git_sync.repos config into database and launches Poller in background goroutine with git checkout base path and auth resolver wiring.
Audit events, dependencies, and documentation
packages/engine/internal/audit/audit.go, packages/engine/go.mod, CLAUDE.md
Adds ActionGitSyncStarted, ActionGitSyncCompleted, ActionGitSyncFailed, ActionGitSyncValidationFailed, ActionGitSyncApplyFailed audit constants. Updates go.mod to include github.com/go-git/go-git/v5, github.com/emersion/go-imap/v2, and golang.org/x/term. Documents mantle repos sync <name> command and Plan B sync engine behavior (reconciliation, polling, auto-apply with manual override).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as repos sync
  participant Engine as SyncRepo
  participant Driver as GoGitDriver/NoopDriver
  participant Discover as Discover
  participant Parser as Workflow Parser
  participant Store as repo.Store
  participant Audit as Audit
  
  User->>CLI: mantle repos sync acme
  CLI->>Store: Load repo config
  CLI->>Engine: SyncRepo(ctx, db, store, repo, driver)
  Engine->>Audit: emit: git.sync.started
  Engine->>Driver: Pull(ctx, repo)
  Driver->>Driver: clone/fetch/reset or return dir
  Driver-->>Engine: PullResult{LocalPath, SHA}
  Engine->>Discover: Discover(LocalPath, path)
  Discover-->>Engine: []DiscoveredFile{RelPath, Bytes, Hash}
  Engine->>Parser: Parse each YAML file
  Parser->>Store: Save workflow if valid
  Store-->>Parser: saved or unchanged
  Engine->>Engine: count applied/unchanged/failures
  Engine->>Store: UpdateSyncState(SHA, error summary)
  Engine->>Audit: emit: git.sync.completed
  Engine-->>CLI: Report{SHA, Applied, Unchanged, Failures}
  CLI-->>User: Synced acme, 2 applied, 0 unchanged, 0 failures
Loading
sequenceDiagram
  participant Startup as serve startup
  participant Config as config
  participant Reconcile as Reconcile
  participant Store as repo.Store
  participant Poller as Poller
  participant Engine as SyncRepo
  
  Startup->>Config: Load git_sync.repos config
  Startup->>Reconcile: Reconcile(ctx, store, repos)
  Reconcile->>Store: Get/Create/Update per config
  Store-->>Reconcile: complete
  Startup->>Poller: New Poller(DB, Store, Driver)
  Startup->>Poller: Run(ctx) in goroutine
  Poller->>Poller: Snapshot enabled auto_apply repos
  Poller->>Poller: Start ticker goroutine per repo
  loop On each tick
    Poller->>Engine: SyncRepo(ctx, db, store, repo, driver)
    Engine->>Engine: Pull/Discover/Parse/Save
    Engine-->>Poller: Report
    Poller->>Poller: OnSync(repo, report, err)
  end
  Poller-->>Startup: (runs in background)
  Startup-->>Startup: Continue server initialization
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

This PR introduces substantial new functionality across the git sync subsystem: multiple driver implementations, authentication handling, file discovery, the core sync orchestration engine, background polling, configuration reconciliation, and CLI/daemon integration. The changes span diverse responsibilities (drivers, auth, discovery, storage, orchestration, scheduling) with moderate logic density and cross-cutting dependencies. Review requires understanding the driver contract, sync flow through the engine, polling mechanics, config reconciliation, and integration with CLI and daemon startup.

Possibly related PRs

  • dvflw/mantle#133: Introduces the foundational repo CRUD store, migrations, and CLI scaffolding that this PR builds upon with sync-state persistence and the full sync engine/poller/CLI integration.

Poem

🐰 A rabbit hops through git repos with glee,
Syncing workflows as far as can be—
With drivers and polls, and credentials so tight,
The MantiSync engine keeps workflows just right! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.71% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: implementing a git sync engine as Plan B of issue #16, which is the core objective of the pull request.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/git-sync-engine

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

❤️ Share

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

michaelmcnees and others added 15 commits May 29, 2026 21:23
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds UpdateSyncState(ctx, id, sha, syncErr) to the repo Store so the
sync engine (Task 7) can record the SHA, timestamp, and error summary
into git_repos.last_sync_* after each attempt. Empty syncErr is stored
as NULL, clearing any previous failure on a successful sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On `mantle serve`, after the lifecycle context is established: reconcile
all git_sync.repos entries from config into the DB (fatal on mismatch),
then launch a Poller with GoGitDriver rooted at <storage.path>/git (or
a tmp fallback). The poller goroutine exits cleanly when ctx is cancelled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Clarify mantle repos Long now that sync ships
- Split ActionGitSyncValidationFailed (parse) from ActionGitSyncApplyFailed (save)
- Wire secret resolver into GoGitDriver.Auth for poller and CLI
- Add NewAuthResolver helper with HTTPS token support
- Document Poller single-snapshot behavior
- Expand --from-dir help and auto_apply:false status hint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
filepath.WalkDir can encounter symlinks; reading via the symlink path
is TOCTOU-prone (CWE-367, gosec G122). Checking d.Type().IsRegular()
before os.ReadFile skips symlinks, device files, and other non-regular
entries, which are not valid workflow YAML anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@michaelmcnees michaelmcnees force-pushed the feat/git-sync-engine branch from 36aa741 to 37f6147 Compare May 30, 2026 02:34
@michaelmcnees michaelmcnees changed the base branch from feat/git-sync-foundation to main May 30, 2026 02:34

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/engine/internal/repo/sync/poller.go (1)

83-96: 💤 Low value

time.NewTicker delays the first sync by a full interval.

With the production 60s default (or 10s floor), no sync runs until the first interval elapses after startup. If a prompt initial sync on boot is desired, fire once before entering the loop.

🤖 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 `@packages/engine/internal/repo/sync/poller.go` around lines 83 - 96, The
poller currently creates a time.NewTicker and waits a full interval before the
first SyncRepo call, delaying initial sync; to fix, invoke SyncRepo(ctx, p.DB,
p.Store, r, p.Driver) once immediately after creating the ticker (and before the
for/select) and call p.OnSync(r, report, syncErr) if set, while still honoring
ctx.Done; keep the existing ticker loop intact for subsequent periodic syncs so
NewTicker/ticker.Stop and the select remain unchanged.
packages/engine/internal/cli/serve.go (2)

168-179: 💤 Low value

Poller goroutine isn't joined on shutdown.

go poller.Run(ctx) is fire-and-forget; when srv.Start(ctx) returns after ctx cancellation, the process can exit before the poller's in-flight SyncRepo completes. If clean drain of an in-progress sync matters, wait for Run to return (e.g., via a channel/WaitGroup) before returning from RunE.

🤖 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 `@packages/engine/internal/cli/serve.go` around lines 168 - 179, The poller
goroutine started with go poller.Run(ctx) is not awaited, so when srv.Start(ctx)
returns on cancellation the process may exit before reposync.Poller.Run
completes; modify the serve command (RunE) to start Run in a managed goroutine
and wait for it to finish before returning: create a sync.WaitGroup or a done
channel, increment it before calling poller.Run(ctx) (or run poller.Run in a
goroutine that signals the done channel), and defer or select to wait for that
signal after srv.Start(ctx) returns (and/or when ctx is canceled) so
reposync.Poller.Run has completed; reference reposync.Poller, poller.Run, and
srv.Start in your changes.

158-167: 💤 Low value

Encryptor is constructed twice from the same key.

Lines 82-90 already build eng.Resolver (with its own secret.Encryptor) when cfg.Encryption.Key != "". This block repeats the same NewEncryptor + Resolver construction. Reuse a single resolver to avoid the duplicate decryption setup and keep the two wirings from drifting.

🤖 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 `@packages/engine/internal/cli/serve.go` around lines 158 - 167, The code is
creating a second secret.Encryptor and secret.Resolver when cfg.Encryption.Key
!= "" (using secret.NewEncryptor and instantiating secret.Resolver/secret.Store)
which duplicates the earlier eng.Resolver construction; instead reuse the
already-created eng.Resolver (or its Store/Encryptor) rather than calling
secret.NewEncryptor again — replace the duplicate NewEncryptor +
secret.Resolver/secret.Store construction with an assignment from the existing
eng.Resolver (e.g., secretResolver = eng.Resolver or secretResolver =
&eng.Resolver.Store as appropriate) so only one Encryptor/Resolver is
instantiated and wired throughout.
packages/engine/internal/repo/store.go (1)

279-296: ⚖️ Poor tradeoff

UpdateSyncState writes state without an in-transaction audit event, diverging from the Store's stated invariant.

The Store doc (lines 15-17) promises every state-changing method emits an audit event in the same transaction "so audit log and state never drift." UpdateSyncState mutates git_repos but emits nothing — the engine emits git.sync.* events in a separate transaction via its own emit helper, so this particular write isn't covered by the same-tx guarantee. If that's an intentional exception (sync state is operational, not security-relevant), consider tightening the Store comment to scope the invariant; otherwise fold the audit emission into this write.

As per coding guidelines: "Emit audit events via the AuditEmitter interface for every state-changing operation."

🤖 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 `@packages/engine/internal/repo/store.go` around lines 279 - 296,
UpdateSyncState currently updates git_repos without emitting an audit event in
the same DB transaction, violating the Store invariant; change it to perform the
update and the audit emission inside a single transaction: start a tx (e.g.,
s.DB.BeginTx(ctx,...)), run the UPDATE using tx.ExecContext, call the Store's
AuditEmitter method (AuditEmitter.EmitAudit or the concrete emit helper) to
record the git.sync.* audit event with repo id, sha and syncErr using the same
tx, rollback on any error and commit on success, and preserve the existing
error-wrapping behavior (keep function name UpdateSyncState and use the same
parameters). Ensure the audit emission uses the AuditEmitter interface so the
audit and state change cannot drift.
🤖 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 `@packages/engine/internal/cli/repos_test.go`:
- Line 179: The test calls reposCtx incorrectly expecting two returns;
reposCtx(t) returns a single *config.Config. Replace the tuple assignment "_,
cfg := reposCtx(t)" with a single assignment "cfg := reposCtx(t)" (update the
variable name usage in the test to match cfg) so the function return count
matches the assignment and the package compiles.

---

Nitpick comments:
In `@packages/engine/internal/cli/serve.go`:
- Around line 168-179: The poller goroutine started with go poller.Run(ctx) is
not awaited, so when srv.Start(ctx) returns on cancellation the process may exit
before reposync.Poller.Run completes; modify the serve command (RunE) to start
Run in a managed goroutine and wait for it to finish before returning: create a
sync.WaitGroup or a done channel, increment it before calling poller.Run(ctx)
(or run poller.Run in a goroutine that signals the done channel), and defer or
select to wait for that signal after srv.Start(ctx) returns (and/or when ctx is
canceled) so reposync.Poller.Run has completed; reference reposync.Poller,
poller.Run, and srv.Start in your changes.
- Around line 158-167: The code is creating a second secret.Encryptor and
secret.Resolver when cfg.Encryption.Key != "" (using secret.NewEncryptor and
instantiating secret.Resolver/secret.Store) which duplicates the earlier
eng.Resolver construction; instead reuse the already-created eng.Resolver (or
its Store/Encryptor) rather than calling secret.NewEncryptor again — replace the
duplicate NewEncryptor + secret.Resolver/secret.Store construction with an
assignment from the existing eng.Resolver (e.g., secretResolver = eng.Resolver
or secretResolver = &eng.Resolver.Store as appropriate) so only one
Encryptor/Resolver is instantiated and wired throughout.

In `@packages/engine/internal/repo/store.go`:
- Around line 279-296: UpdateSyncState currently updates git_repos without
emitting an audit event in the same DB transaction, violating the Store
invariant; change it to perform the update and the audit emission inside a
single transaction: start a tx (e.g., s.DB.BeginTx(ctx,...)), run the UPDATE
using tx.ExecContext, call the Store's AuditEmitter method
(AuditEmitter.EmitAudit or the concrete emit helper) to record the git.sync.*
audit event with repo id, sha and syncErr using the same tx, rollback on any
error and commit on success, and preserve the existing error-wrapping behavior
(keep function name UpdateSyncState and use the same parameters). Ensure the
audit emission uses the AuditEmitter interface so the audit and state change
cannot drift.

In `@packages/engine/internal/repo/sync/poller.go`:
- Around line 83-96: The poller currently creates a time.NewTicker and waits a
full interval before the first SyncRepo call, delaying initial sync; to fix,
invoke SyncRepo(ctx, p.DB, p.Store, r, p.Driver) once immediately after creating
the ticker (and before the for/select) and call p.OnSync(r, report, syncErr) if
set, while still honoring ctx.Done; keep the existing ticker loop intact for
subsequent periodic syncs so NewTicker/ticker.Stop and the select remain
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4134c6b7-8f29-4b3d-8d4d-b9aff7a739bd

📥 Commits

Reviewing files that changed from the base of the PR and between ae7254f and 37f6147.

⛔ Files ignored due to path filters (1)
  • packages/engine/go.sum is excluded by !**/*.sum
📒 Files selected for processing (23)
  • CLAUDE.md
  • packages/engine/go.mod
  • packages/engine/internal/audit/audit.go
  • packages/engine/internal/cli/repos.go
  • packages/engine/internal/cli/repos_test.go
  • packages/engine/internal/cli/serve.go
  • packages/engine/internal/repo/store.go
  • packages/engine/internal/repo/store_test.go
  • packages/engine/internal/repo/sync/auth.go
  • packages/engine/internal/repo/sync/auth_test.go
  • packages/engine/internal/repo/sync/discover.go
  • packages/engine/internal/repo/sync/discover_test.go
  • packages/engine/internal/repo/sync/driver.go
  • packages/engine/internal/repo/sync/engine.go
  • packages/engine/internal/repo/sync/engine_test.go
  • packages/engine/internal/repo/sync/gogit_driver.go
  • packages/engine/internal/repo/sync/gogit_driver_test.go
  • packages/engine/internal/repo/sync/noop_driver.go
  • packages/engine/internal/repo/sync/noop_driver_test.go
  • packages/engine/internal/repo/sync/poller.go
  • packages/engine/internal/repo/sync/poller_test.go
  • packages/engine/internal/repo/sync/reconcile.go
  • packages/engine/internal/repo/sync/reconcile_test.go

Comment thread packages/engine/internal/cli/repos_test.go Outdated
…heck

Gosec G122 (CWE-367) fires on os.ReadFile(path) inside filepath.WalkDir
because the string path is race-prone (symlink TOCTOU). The fix opens the
target directory via os.OpenRoot (Go 1.24+) which uses openat-style
syscalls, then reads each file relative to that root — eliminating the
TOCTOU window without changing the traversal logic.

Also adds 10 go-git transitive dependency vulnerabilities to the
govulncheck exclusion list. govulncheck confirms the call chains are not
reachable from application code ("your code doesn't appear to call").
Track upstream at github.com/go-git/go-git and remove exclusions when
go-git ships fixed dependency versions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@michaelmcnees michaelmcnees merged commit 7d76ee8 into main May 30, 2026
7 checks passed
@michaelmcnees michaelmcnees deleted the feat/git-sync-engine branch May 30, 2026 03:01
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