fix(install): restore static-target deploy path; tighten test fixtures for #1154 strict harness detection#1176
Merged
danielmeppiel merged 1 commit intomainfrom May 7, 2026
Conversation
…s for #1154 PR #1165 set resolved_deploy_root on every v2-resolved static target (phases/targets.py:285, :424). Downstream readers in skill_integrator and base_integrator treat that field as the FINAL deploy destination (it was originally only set by cowork's dynamic-root for_scope), so skills landed at <root_dir>/<skill_name>/ (e.g. .github/<name>/) instead of <deploy_root>/skills/<name>/ (e.g. .agents/skills/<name>/). Source fix: - Stop setting resolved_deploy_root on static targets in both branches of phases/targets.py. Static targets fall back to the standard primitive-mapping path (deploy_root + subdir). Test fixture fixes (#1154 strict harness detection): - integration: drift_check, drift_check_e2e, link_rewrite_e2e, transitive_chain_e2e all needed target: copilot in their apm.yml literals. - integration: link_rewrite_e2e MultiTarget test now passes target=[copilot,claude] explicitly. - release: test-dependency-integration.sh, test-release-validation.sh (gh-aw compat fixture), and the Windows mirror all add target: copilot. - unit: test_targets_phase_v2 was using resolved_deploy_root as a proxy for resolution; updated helper to also accept root_dir for static targets, so it asserts the actual semantic. Local verification: - bash scripts/test-integration.sh --use-existing-binary -> green - bash scripts/test-release-validation.sh -> 6/6 green - uv run pytest tests/unit -> 7779 passed - ruff check + format --check -> silent Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Restores correct deployment routing for static targets by reserving resolved_deploy_root for true dynamic-root targets (cowork), and updates integration/unit fixtures to explicitly declare target: copilot under the post-#1154 strict harness-detection contract.
Changes:
- Stop setting
resolved_deploy_rootfor static v2-resolved targets so integrators honordeploy_root+subdirmappings (e.g., skills to.agents/skills/...). - Update unit/integration tests and validation scripts to include
target: copilot(and explicit multi-target CSV where needed) to avoid exit-2 “No harness detected”. - Adjust a unit-test helper to treat static targets as resolved via
root_dirwhenresolved_deploy_rootis absent.
Show a summary per file
| File | Description |
|---|---|
| src/apm_cli/install/phases/targets.py | Removes resolved_deploy_root assignment for static targets to restore correct deploy-path mapping behavior. |
| tests/unit/install/phases/test_targets_phase_v2.py | Updates helper to derive target root dirs from root_dir when resolved_deploy_root is not set. |
| tests/integration/test_transitive_chain_e2e.py | Adds target: copilot to consumer fixtures to satisfy strict harness detection. |
| tests/integration/test_skill_integration.py | Adds target: copilot to the temp project manifest. |
| tests/integration/test_skill_install.py | Adds target: copilot to manifests used by skill-install tests. |
| tests/integration/test_mixed_deps.py | Adds target: copilot to the temp project manifest. |
| tests/integration/test_link_rewrite_e2e.py | Defaults consumer manifest to target: copilot and sets explicit multi-target CSV for the multi-target scenario. |
| tests/integration/test_drift_check.py | Defaults _make_apm_project(..., target=...) to "copilot" to prevent harness-detection failures. |
| tests/integration/test_drift_check_e2e.py | Writes target: copilot into the minimal apm.yml fixture. |
| tests/integration/test_deps_update_e2e.py | Adds target: copilot to manifests used in deps-update flows. |
| tests/integration/test_cache_lockfile_parity.py | Adds target: copilot to the parity fixture manifest. |
| tests/integration/test_apm_dependencies.py | Relaxes token-prefix assertions to accept additional valid GitHub token prefixes. |
| scripts/test-release-validation.sh | Adds target: copilot to the gh-aw compatibility apm.yml fixture. |
| scripts/test-dependency-integration.sh | Adds target: copilot to dependency-integration apm.yml fixtures. |
| scripts/windows/test-dependency-integration.ps1 | Mirrors the shell script fixture updates by adding target: copilot. |
Copilot's findings
Comments suppressed due to low confidence (1)
tests/integration/test_link_rewrite_e2e.py:387
- The comment above the manual
.claudemkdir is now misleading: this fixture is using an explicittarget: copilot,claudein apm.yml, so the install no longer relies on auto-detection needing both target directories present. Please update/remove the comment so future readers don't assume the behavior is auto-detect-driven.
consumer = _make_consumer(ws, targets=["copilot", "claude"])
# Auto-detect needs both target dirs present. Copilot
# instructions deploy to .github/instructions/; Claude
# instructions deploy to .claude/rules/.
(consumer / ".claude").mkdir()
- Files reviewed: 15/15 changed files
- Comments generated: 0
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.
TL;DR
Two CI failures on the v0.12.3 retag exposed (1) a real source regression where skills deployed to the wrong path, and (2) a fleet of integration/release tests that hadn't been updated for the post-#1154 strict harness-detection regime. This PR fixes both so the full integration AND release-validation suites are green locally end-to-end.
Problem (WHY)
After cutting v0.12.3 (#1170, #1171, #1173) the integration job kept failing. Investigation surfaced two distinct issues:
Source regression in PR fix(targets): explicit, auditable target resolution (closes #1154) #1165:
phases/targets.pystarted settingresolved_deploy_root = project_root / target.root_diron EVERY v2-resolved static target. But that field was originally set only by cowork's dynamic-rootfor_scope(user_scope=True), and downstream readers (skill_integrator.py:751-753, 887-889, 985-986,base_integrator.py:237-242) treatresolved_deploy_root != Noneas "this is a dynamic-root cowork target where the resolved path is the FINAL deploy destination". Result: skills landed at<root_dir>/<skill_name>/(e.g..github/brand-guidelines/SKILL.md) instead of<deploy_root>/<subdir>/<skill_name>/(e.g..agents/skills/brand-guidelines/SKILL.md). Reproduced manually:Test/script fixtures lacking
target:: After Claude skills silently skipped when CLAUDE.md exists but .claude/ directory does not #1154,apm installexits 2 with "No harness detected" when the project has neither a harness signal nortarget:inapm.ymlnor--targeton the CLI. A wide swath of integration tests, release-validation scripts, and even the gh-aw compat fixture (test-release-validation.sh) were still relying on the old "default to copilot" behavior.Approach (WHAT)
Source fix (smallest possible surface): stop setting
resolved_deploy_rooton static targets. Static targets fall back to the standard primitive-mapping path (deploy_rootoverride +subdir), which is the correct logic.resolved_deploy_rootremains reserved for true dynamic-root targets (cowork viafor_scope).Fixture fixes: add
target: copilotto every test/scriptapm.ymlliteral that drivesapm installwithout a harness signal. For tests that intentionally exercise multi-target behavior (link_rewrite_e2e::TestMultiTargetLinkRewriting), pass an explicit list.Unit-test alignment:
test_targets_phase_v2.py::test_three_guard_collapse_no_skipwas added in #1165 and usedresolved_deploy_rootas a proxy for "target was resolved". Updated the helper to accept eitherresolved_deploy_root(cowork) orroot_dir(static), preserving the semantic intent of the test ("target is inctx.targetswith a real on-disk dir") without leaking the proxy detail.Implementation (HOW)
src/apm_cli/install/phases/targets.pyresolved_deploy_root=_target_dirassignments (lines ~285, ~424). Drop now-unuseddataclasses.replaceimports.tests/unit/install/phases/test_targets_phase_v2.py_resolved_dirs->_target_root_dirs(ctx, project_root)that falls back toroot_dirfor static targets.tests/integration/test_drift_check.pytargetarg on_make_apm_projectflipped fromNone->"copilot".tests/integration/test_drift_check_e2e.py_make_projectaddstarget: copilot.tests/integration/test_link_rewrite_e2e.py_make_consumerdefaults totarget: copilot;MultiTargetLinkRewritingnow passestargets=["copilot","claude"]; switch from pluraltargets:to canonical singulartarget:(CSV when multiple).tests/integration/test_transitive_chain_e2e.pytarget: copilot.scripts/test-release-validation.shtarget: copilot.scripts/test-dependency-integration.sh+ Windows.ps1mirrortarget: copilot.Other test-fixture target-additions and one token-prefix relaxation (
test_apm_dependencies.pyacceptsgho_from gh CLI OAuth in addition togithub_pat_*) carried over from earlier work in this batch.Validation
Trade-offs
target.user_root_resolver is not None. Both restore the same invariant; the surgical version preserves the cowork-only contract that downstream code already encodes.generateManifest()will be updated to includetarget:once it consumes >= v0.12.3. Until then, gh-aw users on older apm-action will hit the same exit-2; this is the expected new contract from Claude skills silently skipped when CLAUDE.md exists but .claude/ directory does not #1154 and not introduced by this PR.How to test
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com