Skip to content

test: keep the suite runnable from Codex-managed worktrees and beside a live proxy - #6307

Merged
lidge-jun merged 1 commit into
devfrom
codex/test-guard-managed-worktree
Sep 30, 2026
Merged

lidge-jun merged 1 commit into
devfrom
codex/test-guard-managed-worktree

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

Two host-dependent failures made the suite unusable on a developer machine.

1. Codex-managed worktrees. When the checkout is a Codex-app worktree under ~/.codex/worktrees/, the test-home removal guard refused every path inside the real Codex home. That included the fixture directories forty suites keep beside their test files (join(import.meta.dir, ".tmp-…")). One bun run test:changed from such a worktree produced 863 failures. For example, model-visibility-management-api.test.ts failed 23/23 with refusing to remove a path inside the real Codex home. The same head cloned to /private/tmp produced 56.

The guard now allows one narrow case. It skips the "inside the tree" refusal only for content of the running checkout, and only when that checkout itself sits inside the same protected tree.

  • Equality, ancestor and real-home refusals are unconditional, so the checkout root, its parents and sibling worktrees stay refused.
  • Each path spelling (canonical and lexical) is judged on its own and any refusal wins. A symlink inside the checkout that resolves into the protected tree is therefore still refused.
  • A checkout that contains a protected tree gains nothing. That covers a checkout at $HOME and the virtual root a compiled build reports.
  • Production behavior is unchanged: the checkout roots are computed lazily and only from the removal check, which only test helpers call.

2. shutdown-launcher beside a live proxy. The test started ocx start with a fresh home whose configured port defaulted to 10100. handleStart probes the configured port even when no state files exist. On a machine running ocx on 10100, the child found that proxy, took the sibling path and by design never injected Codex config. SIGINT, SIGTERM and SIGHUP all failed locally, at dev and at every head since; CI has no proxy on 10100. The test now writes { port } to the fixture config.json. It also pins GROK_HOME and OCX_OWNER_REGISTRY_DIR into the fixture, as sibling-home-client-sync.test.ts already does.

Plan and audit: devlog/_plan/260930_local_test_stability/.

Verification

  • bun x tsc --noEmit: pass.
  • From the managed worktree, with a live ocx 2.73.0 answering on 10100:
    • bun test ./tests/service/shutdown-launcher.test.ts ./tests/ci-workflows/test-home-guard.test.ts ./tests/codex-integration/model-visibility-management-api.test.ts: 50 pass, 0 fail. The launcher's 3 signal cases and model-visibility's 23 cases failed before this change.
  • New guard probes:
    • 14-checkout-content builds a sentinel home whose .codex links to the checkout's parent. It asserts that a fixture path is allowed while the checkout root, its parent, a sibling and an escaping link are refused.
    • 15-checkout-contains-tree uses OCX_REAL_HOME = checkout and asserts that .codex and .opencodex content is still refused.
    • With the guard change reverted, probe 14 fails on exactly fixture: false, so it is not vacuous.
  • bun run test:changed results:

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

… a live proxy

A checkout under ~/.codex/worktrees/ (a Codex-app worktree) could not
clean up the fixture directories forty suites keep beside their test
files: the removal guard refused every path inside the real Codex home,
863 failures in one test:changed run. The guard now lifts only the
inside-the-tree refusal, only for content of the running checkout, and
only when that checkout itself sits inside the same protected tree. The
checkout root, its ancestors and siblings stay refused, each path
spelling is judged on its own so a link out of the checkout is still
caught, and a checkout that contains a protected tree gains nothing.

shutdown-launcher started its child with a fresh home whose configured
port defaulted to 10100. On a machine running ocx there, the child found
that proxy, took the sibling path and never injected Codex config. The
test now pins its own port in config.json and pins GROK_HOME and the
owner registry into the fixture.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 30, 2026 08:48
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-30T08:57:04.108545Z e17396c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature). label Sep 30, 2026
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 6ef54427-cada-4ad0-89d5-1f5252617944

📥 Commits

Reviewing files that changed from the base of the PR and between 2405624 and e17396c.

📒 Files selected for processing (5)
  • devlog/_plan/260930_local_test_stability/000_README.md
  • devlog/_plan/260930_local_test_stability/010_diagnosis_and_plan.md
  • src/lib/test-home-guard.ts
  • tests/ci-workflows/test-home-guard.test.ts
  • tests/service/shutdown-launcher.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The change adjusts removal checks for worktrees nested inside protected paths. It also configures the shutdown-launcher test to use its allocated port and test-specific home paths.

Changes

Local test stability

Layer / File(s) Summary
Nested worktree cleanup
devlog/_plan/260930_local_test_stability/*, src/lib/test-home-guard.ts, tests/ci-workflows/test-home-guard.test.ts
The guard resolves the loading checkout and permits removal only for candidates strictly inside a checkout that is itself strictly inside a protected tree. Symlink probes check the exemption and confirm that checkout roots, ancestors, siblings, outward-resolving links, and protected paths within the checkout remain protected. The work-phase documents record the diagnosis, plan, and verification context.
Shutdown-launcher test isolation
tests/service/shutdown-launcher.test.ts
The test writes its allocated port to the temporary home’s config.json and passes test-specific GROK_HOME and OCX_OWNER_REGISTRY_DIR values to the spawned launcher.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to e1739

This change lets the test suite run from nested worktrees and beside a live proxy. The supplied evidence shows the removal guard's existing refusals are preserved, so the merge risk is minimal.

Security Architecture Review

Security architecture risk: 🔵 Low · up to e1739

Cleanup permissions expand only within the running checkout, while protected roots, ancestors, sibling worktrees, and links escaping the checkout remain refused. No production authentication or deletion change was identified. Concurrent filesystem changes remain an unresolved limitation of the cleanup checks.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated authority expansion concerns local filesystem deletion by test cleanup within the loading checkout. Direct callers do not need a registered fixture owner, so the permitted scope is checkout content rather than only directories created by a particular test.

Trust Boundaries and Controls

  • observed — Static symlink aliases are evaluated through canonical target identity as well as lexical spelling. An existing link from checkout content into protected data outside the checkout remains refused. The exception does not relax the separate protected-home write controls.

Resilience and Maintainability Implications

  • inferred — The guard proves containment at authorization time, not throughout recursive deletion. A filesystem topology change between checking and removal remains outside the demonstrated guarantee. The check-then-use structure predates the PR; no increased attacker authority or successful protected-data deletion was established for this change.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files. (2 skipped: 2 …
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes both main changes: supporting tests from Codex-managed worktrees and avoiding failures when a live proxy is present. It is specific, concise, and clearly related to the…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 14 / 80

이 PR은 개발자 노트북에서 테스트가 깨지던 두 가지를 고칩니다. 첫째, Codex 앱이 만든 worktree(~/.codex/worktrees/...) 안에서 돌리면, 테스트가 만든 .tmp-... 폴더를 지울 때 “진짜 Codex 집 안이라서 지우면 안 됨” 가드에 막혀 수백 개가 실패했습니다. 가드는 이제 “체크아웃이 보호 트리 안에 있고, 지우려는 경로가 그 체크아웃 안에만 있을 때”만 그 거부를 풉니다. 체크아웃 루트·부모·형제 worktree는 그대로 막습니다. 둘째, shutdown-launcher 테스트가 포트 없는 새 home으로 ocx start를 띄우면 기본 포트 10100을 들여다보고, 이미 떠 있는 프록시를 형제 인스턴스로 오인해 Codex 설정을 안 넣었습니다. 테스트가 자기 포트와 GROK_HOME·레지스트리 경로를 fixture에 고정해 이 간섭을 끊습니다. base는 dev이고, 프로브 14·15로 예외의 허용·거부 경계를 꽤 단단히 찍었습니다.

라인 - src/lib/test-home-guard.ts isOwnCheckoutContent / checkoutRoots: 예외가 체크아웃 안의 “아무 경로”에 열립니다. 의도는 fixture 정리지만, 버그 테스트가 체크아웃 안 .git·소스까지 remove하면 가드가 막지 않습니다. 가드 원래 임무(실홈·실 Codex 홈 보호) 밖이라 치명적은 아니지만, “체크아웃 안 = 전부 허용”이 의도인지 한 번 더 박아 두는 게 좋습니다.
라인 - devlog/_plan/.../010_diagnosis_and_plan.md Diff: structure에 예외를 적을지 적었는데, 이번 파일 목록엔 structure/ 변경이 없습니다. ops/test-sandbox-cleanup.md 등과 소유권이 갈리면 “안 적음”이 맞을 수 있으니, 의도적 생략인지 확인이 필요합니다.
라인 - tests/ci-workflows/test-home-guard.test.ts 프로브 14: root/.tmp/guard-link-<pid> 심볼릭 링크만 unlink하고, 만든 .tmp 디렉터리 자체는 남길 수 있습니다. 가드 판정과는 무관하지만, 체크아웃에 찌꺼기가 쌓일 여지는 있습니다.

메인테이너의 판단이 필요한 지점
장기적으로 fixture를 체크아웃 옆이 아니라 tmpdir/createTempHome으로만 둘지, 아니면 이번처럼 “Codex worktree 안 체크아웃 content만 허용” 예외를 유지할지. 예외는 좁고 테스트도 있지만, 제거 가드는 과거 실홈 삭제 사고와 맞닿은 면이라 설계 선택이 남습니다. structure 문서 갱신 여부도 같은 묶음입니다.

너의 추천
머지 쪽으로 기울입니다. 예외 조건이 좁고, equality/ancestor/실홈은 그대로이며, 프로브 14·15가 “허용되는 것 / 여전히 거부되는 것”을 나눠 보여 줍니다. launcher 쪽 port·env 고정도 sibling-home-client-sync 패턴과 맞습니다. 머지 전에 structure 미갱신이 의도인지 한 줄만 확인하고, 프로브 14의 .tmp 찌꺼기 정리는 있으면 좋고 없어도 막는 수준은 아닙니다.

이 댓글은 grok-bot이 작성했습니다

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e17396cd87

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +134 to +137
// Inherited real state must not make the child a sibling of the host's proxy either
// (same pinning as tests/cli/sibling-home-client-sync.test.ts).
GROK_HOME: join(home, "grok"),
OCX_OWNER_REGISTRY_DIR: join(identity.homeDir, ".opencodex", "ocx-homes"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Isolate default-home owner discovery on macOS

On macOS with a live proxy in the user's default home, these overrides still do not isolate the child: findCrossHomeOwnerDetailed() separately reads join(homedir(), ".opencodex", "runtime-port.json"), and this repository documents that os.homedir() there ignores the rewritten HOME. OCX_OWNER_REGISTRY_DIR only relocates the registry, so markCrossHomeSibling() can still find the host proxy, mark this child as a sibling, skip Codex config injection, and make all three signal cases time out exactly as before. Add a test-only seam for the default-home lookup and point it at identity.homeDir here.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this against the running host rather than the comment in the repo.

  • findCrossHomeOwnerDetailed resolves the default home as join(options.homeDir ?? homedir(), ".opencodex") (src/cli/cross-home-owner.ts:162). On this macOS host both runtimes honor HOME: HOME=/private/tmp/mm31/fakehome bun -e 'console.log(require("os").homedir())' prints the fake home, and node -e prints the same. The child here gets HOME: identity.homeDir.
  • Empirically: this host has a real ~/.opencodex/runtime-port.json and ocx 2.73.0 answering on 10100, and bun test ./tests/service/shutdown-launcher.test.ts passes all three signal cases from this branch. The same three failed at dev 6538001e2f on the same host.

The passwd-database note in the repo applies to the LaunchAgents path comment. It does not hold for homedir() in these runtimes, so no extra seam is needed for this test.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Maintainer integration into dev (MAINTAINERS.md, dev-only exception), authorized by the owner for this change.

  • Exact head: e17396cd87fd48e03b77361561d7115d97e1a338; all reported checks are pass (21) or skipping (10); Cross-platform CI run 36691993443 succeeded on this head.
  • scripts/ci/assert-mergeable-review.sh --maintainer-integration 6307: see snapshot below.
  • The Codex P2 thread is answered with host evidence: homedir() honors HOME in Bun and Node here, and the launcher test passes beside a live proxy with a real ~/.opencodex/runtime-port.json. No outstanding maintainer change requests.
  • Touches only the test-only removal guard and two tests. No auth, credential, workflow or release-automation path is involved.

@lidge-jun
lidge-jun merged commit cda8ff4 into dev Sep 30, 2026
39 checks passed
@lidge-jun
lidge-jun deleted the codex/test-guard-managed-worktree branch September 30, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant