fix(pull): gate the generic sync report on a tool that can receive it - #751
Merged
Merged
Conversation
The generic branch of the sync loop reported the team repo's item count for every resource type: `Synced N skills` counted what the repo holds, not what landed. Skills are written per tool into that tool's own directory, and a brand-new member has none of them yet — the handler skips such a tool by design and only logs at debug. So the first pull after `init` printed a success while nothing was on disk, which is the phantom-success half of Tencent#585. `hasInstalledTargetFor` asks the same question `getInstalledResourceTargets` already asks, for one resource field, and the generic branch now gates its report on it. Docs need no gate: they are copied to the team's own docs directory, which the copy creates, so that report was already truthful. Only the report is gated. The writes still run, so a tool root created later — Cursor makes `.cursor/` on first launch — is filled by the next pull, and `pull --force` fills it now. Fixes Tencent#585 (the generic branch). Tencent#597 fixed the same shape for rules and left this one open; this closes it for skills.
|
Findings
|
The gate only covered skills, so the generic branch still printed `Synced N agents` when no installed tool could receive them — the same phantom-success shape Tencent#585 describes, one resource type over. AgentsHandler then resolves no destinations and writes nothing. `hasInstalledTargetFor` also duplicated the walk that `getInstalledResourceTargets` already performs. That function now takes an optional `field`, and the gate calls it, so reporting and writing share one resolver instead of two that can drift — an external HERMES_HOME or an unresolvable workspace no longer disagrees between them. Docs, rules and env never reach this branch; hooks and mcp have no tool-path field to probe, so they keep reporting unconditionally. Tests add the agent case the file's header already claimed: no tool directory suppresses the claim and nothing lands, and the claim returns once the directory exists. The suppression case is RED without the gate.
|
Findings
Resolved
|
Merged
3 tasks
Merged
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The generic branch of the sync loop reports the team repo's item count for every resource type. Skills and agents are written per tool into that tool's own directory, and a brand-new member has none of those yet — the handler skips such a tool by design and only logs at debug. So the first pull after
initprintsSynced N skills/Synced N agentswhile nothing reached the disk.This is the phantom-success half of #585. #597 fixed the same shape for rules and explicitly left this branch open as "a known related gap rather than expanding this PR further"; an earlier commit in this branch closed it for skills. This one closes it for agents, which had the identical bug one resource type over.
What changed
getInstalledResourceTargetsnow takes an optionalfield. Without it the behaviour is unchanged ("any of skills/rules/agents"); with it, the walk is restricted to that one resource type. The generic branch calls it forskillsandagentsand gates the report on the result:The previous
hasInstalledTargetForhelper was a second copy of the same walk. It is gone: reporting and writing now share one resolver, so they cannot drift. That was the concrete hazard the review named — an externalHERMES_HOMEcould receive the skill while the success line was suppressed, and an existing~/.openclawwithout a resolvable workspace could still print success while writing nothing.Docs, rules and env never reach this branch (
docsis handled above it,rules/envcontinue earlier).hooksandmcphave notoolPathsfield to probe, so they keep reporting unconditionally.TypeScript narrows
typeto'skills' | 'hooks' | 'agents' | 'mcp'at this point, which is why the condition readstype === 'skills' || type === 'agents'rather than listing every ungated type.Test Plan
Unit tests
src/__tests__/pull-sync-truth.test.tsdrives the realpull()with the git provider, logger and reporting mocked, and seeds a team repo holding a skill, an agent and a doc:Synced N skillsline, noSynced N agentsline, and neither file is on disk; the docs line is still there anddocs/guide.mdreally landedThe RED is real: reverting only the agents half of the gate (
needsToolRoot = type === 'skills') fails the new case with the phantom success itself, not a mock artifact:npx tsc --noEmitexit 0.npm run buildexit 0.End-to-end, real CLI
npm run build, then the compileddist/index.jsagainst a local git team repo (provider: git,--self-style single-repo layout with resources under.teamai/), isolatedHOME, one skill and one agent,toolPaths.codex = { skills, rules, agents }.Codex root absent — the gate suppresses the claim and the run surfaces the failed check instead:
Codex root present — the claim returns:
For contrast, the same first scenario on the pre-fix build still prints
Synced 1 agentswith nothing on disk — that is the bug this PR removes.Not verified
I did not exercise a live
teamai pullagainst a real remote, and I did not runnpm run test:e2e— it needsTEAMAI_TEST_TOKENandTEAMAI_TEST_REPO_URL, which I do not have. The e2e above is against a local git repo, which is the closest I could get. The full agent/provider matrix from AGENTS.md (Claude, Codex, CodeBuddy, OpenCode × git, gitlab, github) is therefore not covered by this record.Related Issues
Fixes part of #585. Follows #597 (rules half) and the skills half of this same gap.
Notes for Reviewers
While building the e2e above I hit a separate, still-open defect that this PR does not fix and that I did not want to smuggle in: with the tool root present, the CLI can print
Synced 1 agentswhile writing no agent file and logging nothing about agents at all — not even the usualSkipping agent sync for <tool>: tool not installeddebug line. The gate above is about "can any tool receive this", so a root that exists is enough to pass it; the write itself is decided further down. Worth its own issue/PR rather than widening this one.