Skip to content

fix(iterate): ship real vendor manifest dirs for Codex install - #607

Open
onatozmenn wants to merge 4 commits into
OpenHands:mainfrom
onatozmenn:fix/iterate-codex-manifest
Open

onatozmenn wants to merge 4 commits into
OpenHands:mainfrom
onatozmenn:fix/iterate-codex-manifest

Conversation

@onatozmenn

@onatozmenn onatozmenn commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

HUMAN:

I ran the targeted VendorManifests tests and verified the lossy-copy case fails on base but passes with the fix, with no new failures or ruff issues beyond the existing ones.


Why

skills/iterate/.codex-plugin is a symlink to .plugin/, and Codex plugin add drops symlinked directories when materializing its install cache. The installed tree then has no .codex-plugin/plugin.json, so Codex fails with "missing or invalid plugin.json" and /iterate never registers (#257).

Summary

  • Replace skills/iterate/.codex-plugin and .claude-plugin symlinks with real directories containing byte-identical plugin.json copies (mode 120000 → 100644).
  • sync_symlinks now accepts a real vendor dir whose plugin.json mirrors the canonical one, flags stale copies, and refreshes them in fix mode; missing vendors still get symlinks as before.
  • New TestVendorManifests tests, including a lossy-install-copy simulation of the reported failure.

Issue Number

Fixes #257

How to Test

  • python -m pytest tests/test_sync_extensions.py -q -k VendorManifests → 4 passed, 2 skipped (symlink-creation tests skip on Windows without privileges; they run on Linux CI).
  • Lossy-copy test fails on base (manifest missing after symlink-dropping copy) and passes with the fix.
  • Full test_sync_extensions.py: the 7 remaining failures are identical on base (Windows-env frontmatter/catalog issues), no new failures. ruff check on touched files: no new findings (2 pre-existing F541s on base).

Type

Bug fix

@github-actions github-actions Bot added the type: fix A bug fix label Sep 18, 2026
@onatozmenn
onatozmenn marked this pull request as ready for review September 18, 2026 13:32
Copilot AI lite review requested due to automatic review settings September 18, 2026 13:32
@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

5 similar comments
@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

@all-hands-bot

Copy link
Copy Markdown
Contributor

👋 This PR needs a couple of things fixed before OpenHands can review it:

  • the PR description's HUMAN: section needs at least 20 characters describing what you tested, not just the template placeholder

Push an update once this is addressed and this check re-runs automatically.

This is an automated check - no AI was used to generate this comment.

Copilot AI 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.

🟡 Changes recommended

Existing plugin-loading tests still require vendor paths to be symlinks, so the full test suite will fail for iterate.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR fixes Codex installation by replacing iterate vendor manifest symlinks with real mirrored directories and updating synchronization checks. It also includes an unrelated GitHub repository monitor profile-resolution change.

Changes:

  • Add real .codex-plugin and .claude-plugin manifest copies.
  • Validate and refresh real vendor manifests in sync_extensions.py.
  • Add manifest/install tests and profile-resolution tests.
File summaries
File Description
scripts/sync_extensions.py Supports symlinked or mirrored vendor manifests.
skills/iterate/.codex-plugin/plugin.json Adds Codex manifest copy.
skills/iterate/.claude-plugin/plugin.json Adds Claude manifest copy.
tests/test_sync_extensions.py Tests vendor manifest synchronization and lossy installs.
skills/github-repo-monitor/scripts/main.py Adds LLM profile resolution.
tests/test_github_repo_monitor.py Tests profile resolution behavior.
Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +320 to +324
for vendor in (".codex-plugin", ".claude-plugin"):
path = iterate / vendor
assert not path.is_symlink(), f"{vendor} must not be a symlink"
assert path.is_dir(), f"{vendor} must be a real directory"
assert (path / "plugin.json").read_bytes() == canon
Comment on lines +427 to +432
profile = os.environ.get("AUTOMATION_MODEL")
if profile:
profile_llm = _fetch_profile_llm(agent_url, api_key, profile)
if profile_llm is not None:
llm = profile_llm
llm["usage_id"] = f"profile:{profile}"
Co-authored-by: openhands <openhands@all-hands.dev>
@onatozmenn
onatozmenn force-pushed the fix/iterate-codex-manifest branch from a44d970 to c1ee8ba Compare September 18, 2026 14:31
Co-authored-by: openhands <openhands@all-hands.dev>
@onatozmenn

Copy link
Copy Markdown
Contributor Author

Follow-up on the review: the branch has been cleaned to just this fix, and the valid point about plugin-loading tests is addressed — test_all_marketplace_skills_have_vendor_manifests and test_has_vendor_manifests in tests/test_skill_plugin_loading.py now accept a real directory mirroring plugin.json alongside symlinks, matching the new sync rule. The iterate cases pass; remaining failures in that file are pre-existing Windows-checkout artifacts (symlinks materialize as files), identical on base.

@all-hands-bot all-hands-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.

This review was posted by an AI agent (OpenHands).

Assessment

The core fix is sound: replacing the skills/iterate/.codex-plugin (and .claude-plugin) symlinks with real directories whose plugin.json mirrors .plugin/plugin.json does survive the symlink-dropping copy that Codex performs, and the added sync_symlinks handling correctly accepts a real mirror directory, flags a stale copy, and refreshes it in fix mode. The test_lossy_install_copy_keeps_codex_manifest regression test genuinely reproduces the reported failure.

However, the new unit test TestVendorManifests.test_missing_vendor_still_gets_symlink fails on Linux, which is the platform the Tests workflow runs on (.github/workflows/tests.yml runs uv run --group test pytest tests/ on ubuntu-latest). This contradicts the PR's "How to Test" section, which reports 4 passed, 2 skipped. Reproduced against the exact head:

$ uv run --with pytest --with pyyaml python -m pytest tests/test_sync_extensions.py -q -k VendorManifests
.....F                                                                   [100%]
FAILED tests/test_sync_extensions.py::TestVendorManifests::test_missing_vendor_still_gets_symlink
AssertionError: assert ['missing: sk...codex-plugin'] == []
1 failed, 5 passed, 34 deselected

The 4 passed, 2 skipped figure is the Windows-without-privileges result: there, creating the symlink raises OSError and the test skips. On Linux symlinks are created without error, so sync_symlinks(check=False) returns the two missing: ... problem strings (fix mode reports everything it fixed) and the assertion fails. The except OSError guard never triggers on Linux, so the test cannot pass there. The repository's own test suite is therefore red on the current head.

Non-blocking design note

The fix is applied only to skills/iterate; the other ~64 skill/plugin directories with a .plugin/ manifest still use vendor symlinks on this head, and sync_symlinks keeps creating symlinks for any directory missing a vendor path. Since the reported root cause (Codex plugin add dropping symlinked directories) is not specific to iterate, those plugins would hit the same "missing or invalid plugin.json" failure when installed as Codex plugins. Consider whether the sync script should emit real mirror directories by default (or document why iterate is special) so this class of regression cannot recur for new skills.

🔄 CHANGES REQUESTED

Comment thread tests/test_sync_extensions.py Outdated
skill = _make_plugin_skill(tmp_path)
_point_sync_at(tmp_path, monkeypatch)
try:
assert sync_symlinks(check=False) == []

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.

This assertion fails on Linux (the platform CI uses), so the PR's own test suite is red on the current head. In fix mode sync_symlinks(check=False) returns the missing: skills/demo/.claude-plugin / missing: skills/demo/.codex-plugin problem strings for the symlinks it just created, so == [] is never true when symlink creation succeeds. except OSError only catches the Windows-without-privileges path (where the skip makes the test green); on Linux it reaches the assert and fails. Reproduced with uv run --with pytest --with pyyaml python -m pytest tests/test_sync_extensions.py -k VendorManifests (1 failed, 5 passed). Assert on the created symlinks instead - drop the == [] check and keep the is_symlink() assertions below, or expect the two missing: entries.

@onatozmenn

Copy link
Copy Markdown
Contributor Author

Fixed the Linux failure: test_missing_vendor_still_gets_symlink no longer asserts sync_symlinks(check=False) == [] (fix mode returns the missing: entries it just fixed). It now asserts the symlinks were created and that a follow-up check=True run is clean.

@all-hands-bot all-hands-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.

This review was posted by an AI agent (OpenHands).

Assessment

The change is correctly scoped to this repository: it fixes a Codex plugin-loading defect in the skills/ registry that this repo owns, and touches only the sync tooling, the skills/iterate vendor manifests, and the related tests.

The fix is sound. Replacing the skills/iterate/.codex-plugin and .claude-plugin symlinks with real directories whose plugin.json is byte-identical to .plugin/plugin.json survives the symlink-dropping copy that Codex plugin add performs, and the added sync_symlinks branch correctly treats a real vendor directory as valid when its manifest mirrors the canonical one, flags a stale copy, and refreshes it in fix mode. Missing vendors still get symlinks as before.

The previously reported failure on TestVendorManifests.test_missing_vendor_still_gets_symlink is resolved by commit 7d657fb, which stops asserting sync_symlinks(check=False) == [] (fix mode returns the missing: problem strings for paths it created) and instead verifies the created symlinks plus sync_symlinks(check=True) == [].

Verification on this head

  • uv run --with pytest --with pyyaml python -m pytest tests/test_sync_extensions.py -q -> 40 passed.
  • uv run --with pytest --with pyyaml --with openhands-sdk python -m pytest tests/test_skill_plugin_loading.py -q -> 78 passed.
  • python scripts/sync_extensions.py --check exits 0 (only the non-blocking pre-existing coverage warning for ./plugins/issue-duplicate-checker, unrelated to this PR).
  • GitHub Actions for head 7d657fb: Tests, Check Extensions, PR Artifacts, PR Description Check, Deprecation deadlines and pr all completed/success.
  • The lossy-copy regression test_lossy_install_copy_keeps_codex_manifest (#257) is present and passes.

No blocking or non-blocking findings remain.

✅ APPROVED

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/iterate still fails to load in Codex because .codex-plugin symlink is lost during plugin install

3 participants