Conversation
…rise#703)
Invariant G3: human and fleet work reaches the agent within a bound.
Nothing inside the container ever pulled; the 2026-09-24 audit found
agents up to 31 commits behind.
- agent server: a pull loop beside the push loop, gated per cycle on
the owner's pull_sync_enabled (read live, GIT_SYNC_PULL fallback),
interval GIT_SYNC_PULL_INTERVAL_SECONDS (defaults to the push one).
Under _REPO_LOCK, never while an execution runs (checked again right
before the tree is touched). Fast-forward, or rebase aborted on
conflict. Uncommitted edits are stashed explicitly and a pull that
collides with them is undone - not --autostash, which strands them
in the stash while reporting success.
- pull_sync_enabled + last_pull_at/_status/behind_after_pull on both
migration tracks; backfill on only where auto-sync is on; new github
agents get it at creation (source mode included), GIT_SYNC_PULL env
derived from the DB flag alone at recreate.
- GET/PUT /api/agents/{name}/git/pull-sync; sync-health persists the
pull outcome (bounded); Settings -> Git sync gains the toggle.
Stacked on #3020 (+#3015-#3018): merges after it.
Related to Abilityai/trinity-enterprise#703
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nto feature/ent703-pull-heartbeat
…eSQL (trinity-enterprise#703)
The inline comment on `pull_sync_enabled` sat between the comma and the
FOREIGN KEY clause. `_PG_TABLE_SUBS` strips that clause only when it directly
follows the comma, so the clause survived without its REFERENCES and
PostgreSQL rejected the table ("syntax error at or near ')'"), failing 18
requires_postgres tests in schema-parity. The comment moves above the column.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
merge-train: ejected from this train because it is stacked on #3020, which is ejected pending a ruling (see #3020). On its own delta this is READY: single Alembic head ( One intent question to settle while it waits: |
|
Resolve by running |
|
Resolve by merging |
|
|
AndriiPasternak31
left a comment
There was a problem hiding this comment.
Request changes. The design is right: a separate loop, the live flag, an explicit stash instead of --autostash, and push fields kept apart. The real-repo tests are good. I ran them (19/19) plus a few probes against _run_pull_once. Blocking items:
alembic-head-watchis red because of this PR.devnow has0076_operator_queue_ask_objectthrough0079_telegram_group_contextoff0075, and0076_pull_syncstill revises0075, which gives two heads. Rebase, renumber to0080_pull_syncwithdown_revision = "0079_telegram_group_context", and re-resolvedb/migrations.py. The PR is also CONFLICTING in 12 files._integrate_remotecan strand the stash, which is the case the docstring exists to prevent (routers/git.py:876-903).run_registeredraisesTimeoutExpired, so a timed-outmerge --ff-only,stash poporreset --hardskips every restore. Repro: fast-forward times out →failed, the edit is gone from the tree and sits instash@{0}, and the error doesn't mention the stash. Please put atry/finallyaround the post-stash section, and make the error say when a stash entry is left.- The return code of
reset --hardis ignored at:898. If it fails (e.g.index.lockheld by the backend's docker-exec gitignore sweep, which runs outside_REPO_LOCK), the tree is leftUUwith conflict markers. The next auto-sync cycle'sgit add -Acommits them and pushes them to origin with statussuccess. I reproduced this end to end. Please check the return code and stop loudly. I'd also make the push cycle refuse to commit whilediff --diff-filter=Uis non-empty. - The execution gate misses queued turns (
:830).list_running()excludesregister_pendingentries (#2433), so a turn waiting on the chat lock or the headless pool counts as idle. Please countlist_pending_ids()as busy too.
Non-blocking, but worth resolving before merge:
- Where the gate actually protects anyone. For auto-sync agents, the #3011 push cycle already fetches and rebases every cycle with no execution gate. So for the backfilled population the pull is mostly redundant, and the UI line "Never runs while the agent is working" isn't true. Existing source-mode agents, the ones that need the pull, stay off until toggled. Worth saying so in the PR, and probably gating #3011's rebase the same way.
- Pull failures are write-only.
last_pull_errorisn't persisted, there is nolast_successful_pull_at, nothing feedssync_failingor/sync-health, and repo-busy skips aren't recorded. ent#706/#707 will needlast_successful_pull_at, a persistedlast_pull_error, and consecutive pull failure/skip counts. Cheaper to add them to this migration pair than to open a second one on the same table. behind_after_pullis own-branch behind. On atrinity/*agent it reads 0 whilebehind_mainis 1. Please document it.- Docs.
architecture/agent-runtime.md:27still says "two loops". The UI label hard-codes 15 minutes, butGIT_SYNC_PULL_INTERVAL_SECONDSmakes it configurable. The pull cycle also never reaps a staleindex.lock, which matters for pull-only agents.
|
Small correction to item 1 of my review: don't hard-code |
…nto feature/ent703-pull-heartbeat # Conflicts: # src/backend/db/migrations.py # tests/registry.json
…ns, brings main to working branches (trinity-enterprise#703) PR #3021 review: - _with_stash wraps every post-stash step: a git child that times out (run_registered raises TimeoutExpired) goes back to the pre-pull HEAD and re-applies the stash; when that is impossible the error says the edits are kept in `git stash`. - reset --hard's return code is checked. A failed undo stops with an error naming it instead of leaving UU files behind silently. - Both cycles refuse to run over unmerged paths. The push cycle checks before `git add -A`, which would stage conflict markers and push them to origin as a successful sync. - The execution gate counts register_pending entries (#2433), so a turn queued on the chat lock or the headless pool is busy, not idle. - The pull reaps stale lock litter first, as the push cycle does; a pull-only agent has no push cycle to do it. - sync-state gains last_pull_error streak fields: consecutive pull failures / skips and last_successful_pull_at. Ruling on the intent question: a trinity/* working branch only ever pulled itself, so human pushes to main never arrived (G3). The cycle now also merges origin/main (via _get_pull_branch) into the working branch — a merge, not a rebase, since the branch is already pushed; a conflict is aborted and recorded. Nine real-repo tests, all red before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… + UI copy (trinity-enterprise#703) - Alembic 0076_pull_sync -> 0080_pull_sync <- 0079_telegram_group_context (one head, 81 revisions); SQLite entry re-appended after dev's. - The same migration pair adds agent_sync_state.last_pull_error, last_successful_pull_at, consecutive_pull_failures and consecutive_pull_skips, persisted by the sync-health poller (bounded, coerced; a success clears the error). ent#706/#707 read these, so they land here rather than as a second migration on the same table. - agent-runtime.md says three loops; the flow and requirement describe the main merge, the queued-turn gate, the unmerged-path refusal, what behind_after_pull measures, and that auto-sync agents already rebase in the push cycle. - Settings copy no longer hard-codes 15 minutes (GIT_SYNC_PULL_INTERVAL_SECONDS) and no longer claims the push cycle never touches the tree while the agent works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
All four blocking items, the intent question and most of the non-blocking ones are in. The branch re-merges #3020, which now carries Blocking (Andrii)
Intent (vybe): a Non-blocking
Tests
|
|
Ownership moves to @AndriiPasternak31 (ent#703 handover, per the Mon–Wed plan). The branch is ready for your re-review; I won't push to it further unless you ask. |
AndriiPasternak31
left a comment
There was a problem hiding this comment.
Approve. All four blocking items from my 09-27 review are fixed, each with a real-repo test, and the "bring main into working branches" follow-up is implemented. I found nothing new that blocks. There are three should-fix items below: two are small and cheap to land before merge, the third is at least a wording fix. I reviewed only this PR's own commits (ee8929840..8c1282456, diffed against #3020's head 8b20c1714). #3020 is reviewed separately, and this PR merges after it.
Previous blocking items
- ✅ 1. Alembic head fork.
0080_pull_sync.pynow revises0079_telegram_group_context, and the SQLitepull_syncentry comes afterdev's.check_alembic_heads.pyreports 81 revisions and 1 head (0080_pull_sync).alembic-head-watchis green, and the PR is MERGEABLE. #2984 and #3005 both carry an0080_*on0079and are both mergeable, so whichever of the three lands later has to re-chain. Re-run the head check right before merge. - ✅ 2. Stash stranded on timeout. Every step after the stash now runs inside
_with_stash(routers/git.py:891-957). OnTimeoutExpiredit resets topre_headand pops. If that can't be done, the error ends with "local edits are kept ingit stash". Tests:test_a_timed_out_fast_forward_puts_the_edits_backandtest_a_stash_left_behind_is_named_in_the_error. - ✅ 3.
reset --hardreturn code ignored / markers pushed. The return code is checked now: a failed reset returns "…could not be undone…". Both cycles refuse to run while_unmerged_pathsis non-empty, and the push cycle checks beforegit add -A(:1159). My end-to-end repro is nowtest_a_failed_undo_stops_loudly_and_the_push_refuses_the_markers: the push returnsrefused: unmerged paths (notes.md),origin/mainis unchanged, and no markers reach it. - ✅ 4. Queued turns counted as idle.
_executions_in_flightreturnslist_running()+list_pending_ids()(:830-845). Test:test_a_queued_turn_counts_as_busy. - Non-blocking items from last time:
- Done: pull health persisted (
last_pull_error,last_successful_pull_at, pull failure/skip streaks), stale-lock reaping before the pull,behind_after_pulldocumented, "three loops" inagent-runtime.md, and the UI copy no longer hard-codes 15 minutes. - Deferred with a stated reason: execution-gating #3011's push-cycle rebase. That's fine as its own change.
- Done: pull health persisted (
- ✅ "Pull
maininto working branches."_integrate_source(:975-994) mergesorigin/<main>into atrinity/*branch every cycle and aborts on conflict. Tests:test_main_is_merged_into_the_working_branchandtest_a_conflicting_main_is_aborted_and_recorded. The PR body still lists this as out of scope, so please update it.
New findings
-
[should-fix] A conflict merging
mainis recorded as "Auto-merging <file>".docker/base-image/agent_server/routers/git.py:991- Problem: on a conflict, git prints to stdout and leaves stderr empty.
_summarize_git_error(merge.stderr or merge.stdout)therefore takes stdout's first line, which is the auto-merge notice, not the conflict. - Reproduced on a real repo: the agent commits
notes.mdon its working branch, a human commitsnotes.mdonmain, and_run_pull_oncereturns{'status': 'failed', 'error': 'merging main: Auto-merging notes.md'}. That exact string is also persisted aslast_pull_error. - Failure scenario: every 15 minutes the same failure repeats with an error that reads like progress. ent#706/#707 will surface this field directly, and an operator can't tell it is a conflict.
- Also here: the
merge --abortreturn code is ignored (:990). The unmerged-paths guard does stop the push, but the error doesn't say the abort failed. - The current test only asserts
startswith("merging main: "), so it doesn't catch this. - Fix: mirror
_rebase_onto_remote. Read--diff-filter=U(or look forCONFLICTin the output) before aborting, and returndiverged: merge conflict with main (notes.md). Check the abort's return code and say so if it failed. Make the test assert the conflict wording.
- Problem: on a conflict, git prints to stdout and leaves stderr empty.
-
[should-fix] Any rebase of a working branch silently flattens the merge commit the pull just made.
routers/git.py:803(git rebase --autostash origin/<branch>), reached from_integrate_remote(:970) and the push cycle (:1205)- Problem: plain
git rebasedrops merge commits. It replaysmain's commits one by one onto the working branch as new SHAs. - Reproduced: auto-sync is off, cycle 1 merges
mainintotrinity/a/1(1 merge commit, not pushed), then a human pushes toorigin/trinity/a/1. Cycle 2 returnssuccess, butgit log --mergesis empty,main's commit reappears as a copy, andorigin/mainis no longer an ancestor of HEAD. - Failure scenario:
behind_mainkeeps reporting the agent as behind even though the content is there. The next cycle mergesmainagain on top of the duplicates, and the history the agent pushes carries copied commits. - The PR's own reasoning for merging rather than rebasing ("rewriting it would make the next push cycle rebase it back") is exactly what this undoes.
- Rare today: it needs an unpushed merge plus someone else pushing to the agent's own branch. It gets likelier once pull stays on while auto-sync is toggled off.
- Fix: use
rebase --rebase-mergesin_rebase_onto_remote, or merge instead of rebasing when the branch istrinity/*. Add a test for this sequence.
- Problem: plain
-
[should-fix] "Never while a turn runs" is check-then-act.
routers/git.py:1072-1082- Problem: the gate is read before
_integrate_remote/_integrate_sourcerun, but nothing on the admission side waits for it.register_pendinginchat.py:53/204,claude_code.py:750andresult_callback.py:418never looks at_REPO_LOCKor at a pull in progress. - Failure scenario: a turn admitted during the integrate window (a fast-forward or merge of up to 60 s, a rebase up to 120 s, plus the stash operations) reads files while HEAD is moving. That is the case the issue's acceptance criterion rules out ("serialize on
_REPO_LOCKand the execution lock"). - The claim appears as an absolute in three places: the Settings copy (
GitSyncSettingsPanel.vue:75, "never runs while the agent is working"), requirement §11.18 andagent-lifecycle.md. - Fix: hold admission briefly while a pull is integrating (for example, a pull-in-progress event that
register_pendingcallers wait on, with a bound). Or, at minimum, change the copy to "never starts while…" and document the window.
- Problem: the gate is read before
-
[nit] A failed ahead/behind count reads as "up to date."
routers/git.py:1065-1070_compute_ahead_behindreturns(0, 0)on any failure, including its 10 s timeout. The pull then recordssuccesswithbehind_after_pull=0and moveslast_successful_pull_atforward.- Fix: give the pull cycle a strict variant that fails the pull instead.
-
[nit] A timed-out step with nothing stashed is left as the kill left it.
routers/git.py:946-957- The reset back to
pre_headonly happens whenstashedis true. - Failure scenario: a killed
merge --ff-onlyormergeon a clean tree can leave a half-updated tree or aMERGE_HEAD. The next push cycle can then commit that partial state as the agent's own work. - Fix: reset to
pre_headon timeout whether or not anything was stashed. If anindex.lockfrom the killed child blocks the reset, the error should say so.
- The reset back to
-
[nit] The PR description is stale.
- "Out of scope: merging the source branch into a working branch" is now in scope.
- The test count still says 19; there are 28 now.
- "Live on local dev" is still unchecked. The loop's startup wiring (
schedule_auto_sync_if_enabled) and the real process-registry import have only run under unit tests, never inside an agent container.
What I checked
- Read this PR's own diff against #3020's head. That covers the agent server (
auto_sync.py,routers/git.py), both migration tracks and the schema, thepull-syncroutes, creation/recreate env (crud.pyandlifecycle.py), the sync-health poller, the UI panel and the docs. - Architecture checks:
- Creation and recreate don't touch the PAT gate or the push blackhole; the pull only fetches.
- Errors pass through
_summarize_git_error, which redacts URL userinfo. - The pull never writes the push's
consecutive_failures, so the freeze still keys on push health. The newrefused: unmerged pathscounts toward the freeze, which is the right direction. - The reaper reuses the #1595 1-hour age gate. It never removes a live
index.lock. - Shutdown cancels both loop tasks.
- Tests: 16 touched and related unit files (pull cycle, #3010, #3011, auto-sync, #1595, #2742, sync health/state, ent109 env seam, #1484, fork-to-own, pull branch, dual ahead/behind). 429 passed + 1 skipped on seed 12345, 430 passed on seed 99999.
- Real-repo probes (bare origin, agent clone, human clone) for findings 1 and 2.
check_alembic_heads.py: 1 head.gh pr checks 3021: every check passing or skipped, none failing.
#3005 landed Alembic 0080_agent_skill_sets on 0079_telegram_group_context, the same parent this branch's 0080_pull_sync used, so merging as-is would leave two heads and `upgrade head` would apply nothing. Renumber the revision to 0081_pull_sync, chained on 0080_agent_skill_sets (file, revision, down_revision, docstring, the SQLite mirror note and the revision test). SQLite MIGRATIONS keeps dev's agent_skill_sets entry first, pull_sync after it. tests/registry.json keeps both entries. No logic change.
|
Merged
Merge order: #3035 adds @vybe could you review? I approved this earlier today, but I've pushed to it since, so it needs an approver other than me (SOC 2). |
Dismissing my own approval: I also pushed commits to this branch (the dev merge fixing the 0080 migration fork), so this approval would cover my own code (SOC 2 separation of duties). Needs an independent review.
…nto feature/ent703-pull-heartbeat # Conflicts: # tests/unit/test_1484_create_agent_characterization.py
…p merges (trinity-enterprise#703) PR #3021 re-review: - A conflict merging main was recorded as "Auto-merging <file>": git prints it on stdout with an empty stderr. Unmerged paths are read before the abort; the error is "diverged: merge conflict with main (<files>)", and a failed merge --abort is named. - `git rebase` flattened the merge the pull made into copies of main's commits; both cycles now rebase with --rebase-merges. - A failed strict ahead/behind count fails the pull instead of reading as up to date. - A timed-out step is reset to the pre-pull HEAD even with nothing stashed; a reset blocked by the killed child's index.lock says so. - "Never runs while the agent is working" is check-then-act: the copy and docs now say "never starts" and document the admission window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Addressed the 09-28 re-review:
Verification
🤖 Generated with Claude Code |
…nto feature/ent703-pull-heartbeat # Conflicts: # tests/registry.json
|
merge-train: not on train #3070 — the code validated READY (both migration tracks consistent, single Alembic head Non-blocking notes: backend |
obasilakis
left a comment
There was a problem hiding this comment.
Approve on content.
- Security: the new endpoints use the same auth as auto-sync (reading the flag needs agent access, changing it needs ownership). The pull refuses to run over unresolved conflicts, and the push refuses to commit them. Status text written by the agent is limited to the known values and truncated before it is stored.
- Both migration tracks are present. Merged into dev, the Alembic history has a single head (
0081_pull_sync). - The PR's tests pass locally (178). Two deliberate code breaks were each caught by a test: removing the push cycle's refusal to commit unresolved conflicts, and dropping
--rebase-merges.
Follow-ups (non-blocking):
- File the issue for the window between the idle check and the pull: a turn admitted during a pull can see files change mid-read.
- Add backend tests for
GET/PUT .../git/pull-sync: a non-owner is rejected, and 404 when git is not configured. - The
# mcp:header inrouters/git.pydoes not mention pull-sync. - Release notes: agents with auto-sync already on get pull turned on at upgrade and will merge
maininto their working branch every cycle.
Merge after #3020. tests/registry.json will conflict once #3020 lands; resolving it is a small by-hand merge.
…eartbeat Brings the updated base (with current dev). Resolve: registry = base file plus the ent#703 entry; agent-lifecycle keeps the pull-cycle paragraph beside the base's updated agent-side paragraph; git-sync-health keeps both new sections (1c ent#708, 1d ent#703). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/ent703-pull-heartbeat dev gained 0081_portal_messages_unread_idx (#3076). Resolve: - db/migrations.py: keep dev's portal_messages_unread_index entry, then pull_sync. - Alembic: 0081_pull_sync -> 0082_pull_sync, down_revision 0081_portal_messages_unread_idx, so the version line keeps a single head. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
merge-train: not on this train — rides the next train once fixed. Race in the pull cycle: the "no execution running" check happens before the pull, but nothing holds turn admission during it. Also noted, not blocking: Heads-up for sequencing: #3035 also adds |
|
Migration clash with current This branch adds Fix before merge:
#3022 carries this migration and will need a re-merge of #3021 after that. |
… revision to 0083 dev gained 0082_agent_sync_state_divergence (#3035), which adds its own agent_sync_state columns. Union both column sets across schema/tables/ sync_state/sync_health_service, chain 0083_pull_sync off dev's head, and order the SQLite entry after agent_sync_state_divergence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2996's route census (merged to dev) requires every new route to be classified. The agent's pull loop reads GET .../git/pull-sync with its own key each cycle (AGENT_CALLABLE); the PUT is a setting write, so it takes Depends(require_person) like the other #2996 settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
merge-train: not on this train. It rides the next one once fixed. The migration renumber is right: Yesterday's ejection reason is still open. The two commits since then (
Also noted, not blocking:
|
dev moved past 0082 (0083 to 0085 landed), so 0083_pull_sync forked the Alembic graph into two heads, which alembic-head-watch flagged. It is now 0086_pull_sync on 0085_ent720_email_identity, and the SQLite list keeps both sides, with dev's entries first and pull_sync after. The merge also brings #3107, the agent-server boot fix whose absence failed journey-smoke. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dev moved past 0082 (0083 to 0085 landed), so 0083_seat_ask_class_state forked the Alembic graph into two heads, which alembic-head-watch flagged. It is now 0087_seat_ask_class_state on 0085_ent720_email_identity. 0087 rather than 0086 because #3021's pull_sync takes 0086; whichever of the two merges second re-parents onto the other. The SQLite list keeps both sides, with dev's entries first. The merge also brings #3107, the agent-server boot fix whose absence failed journey-smoke. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
merge-train: not on today's train. I've labelled this |
Summary
Invariant G3: human and fleet work reaches the agent within a bound. Nothing inside the container ever pulled, and the 2026-09-24 fleet audit found agents up to 31 commits behind.
pull_sync_enabled. The flag is read live fromGET .../git/pull-syncwith the agent's own key, withGIT_SYNC_PULLas the fallback (the bug(git-sync): the auto-sync toggle is not authoritative — OFF never takes effect (the baked env is OR'd forward at recreate), ON waits for the next recreate #3010 pattern). IntervalGIT_SYNC_PULL_INTERVAL_SECONDS, defaulting to the push interval (900 s)._run_pull_once, under_REPO_LOCK):list_running()+list_pending_ids()). Checked from the process registry, and again right before the tree is touched; an unreadable registry counts as busy. This is check-then-act: turn admission does not wait on a pull, so a turn admitted during the integrate window can see HEAD move (documented; holding admission is a follow-up).--rebase-merges), aborted on conflict.trinity/*working branch also mergesorigin/mainin (_integrate_source), so human pushes tomainarrive. A conflict is aborted and recorded asdiverged: merge conflict with main (<files>).--autostash. When incoming commits touch the same files, autostash's re-apply conflicts, leaves the edits only in the stash, and still reports success. Here a colliding pull is undone (back to the pre-pull HEAD, where the stash applies cleanly) and recorded.pull_sync_enabledon both migration tracksauto_sync_enabledis already ongithub:agents get it at creation, source mode included (pull-only agents are the ones that most need it); ghosts excludedGIT_SYNC_PULLderived from the DB flag alone at recreatelast_pull_at/last_pull_status/last_pull_error/behind_after_pull/last_successful_pull_at/consecutive_pull_failures/consecutive_pull_skipsinsync-state.json, persisted onagent_sync_state(status bounded to its vocabulary, counter coerced). A failed pull never touches the push'sconsecutive_failures.maininto a working branch, every cycle.sync_failing//sync-health(ent#706/bug: workspace canvas panel flickers + charts broken in update_panel #707)Changes
auto_sync.py:run_pull_loop/run_one_pull_cycle/resolve_pull_sync_enabled; the live flag read is generalised (_resolve_flag) and shared with the push looprouters/git.py:_run_pull_once,_with_stash,_integrate_remote,_integrate_source,_unmerged_paths,_executions_in_flight,_record_pull;_rebase_onto_remoteuses--rebase-merges;/api/git/statusreportspull_sync_enabledpull_sync+ Alembic0081_pull_sync←0080_agent_skill_setsdb/schedules/git_config.py,db/sync_state.py,db_models.py,database.pyrouters/git.py+models.PullSyncToggle:GET/PUT .../git/pull-synccrud.py: on at creation + envlifecycle.py: env from the DB flagsync_health_service.py: persists the pull outcomeGitSyncSettingsPanel.vue,stores/agents.jstest_ent703_pull_cycle.py(33, real repos)test_1484(+2: creation turns pull on for agents and deployments)test_ent109_git_env_seam(+2: env follows the DB flag alone; the owned-key guard fixture now writes every owned var)test_sync_health_service(+2: persisted, and agent-written garbage dropped)gitSyncSettingsPanel.spec.js(+2, mounted)requirements/github.md§11.18feature-flows/git-sync-health.md§1darchitecture/agent-lifecycle.mdarchitecture/api-endpoints.mdTest Plan
148462,ent10924,sync_health_service49vitest run3632/3632 (ratchets included);check:tokensOK_integrate_remotewith a naiverebase --autostashfails exactly the colliding-edits testlint_sys_modules, root placement, enterprise-docs guard clean; single Alembic head (0081_pull_sync)Related to abilityai/trinity-enterprise#703 (cross-repo; close at release)
🤖 Generated with Claude Code