Skip to content

fix(stop): name the real refusal cause instead of asserting ownership - #4237

Merged
lidge-jun merged 2 commits into
devfrom
codex/260911-l4-stop-refusal
Sep 11, 2026
Merged

fix(stop): name the real refusal cause instead of asserting ownership#4237
lidge-jun merged 2 commits into
devfrom
codex/260911-l4-stop-refusal

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

POST /api/stop refuses for three distinct reasons, but when no readable message arrived stopProxy fell back to one hardcoded sentence naming a fourth cause the server never reports — a CODEX_HOME/OPENCODEX_HOME ownership mismatch. In the reported case the homes matched exactly and the server had answered respawnable_service, so three attempts went into re-exporting CODEX_HOME before the endpoint was probed directly.

Carried from #4170 by @yeongjunyoo. That PR captures the refusal code next to the message, selects the fallback wording from it, and returns the refusal per attempt instead of publishing it to module state, so two overlapping stops cannot lend each other the wrong cause.

This also closes the second half of the issue, which the carry left open. #4169's Expected section asks for two things: that a refusal not be described as an ownership mismatch, and that "the recommended next command should not be the command the operator just ran." Only the first was addressed. ocx stop reaches POST /api/stop through handleStopstopWithDeferralstopProxy, and the server answers "the stop must be run by ocx stop" because that refusal is written for an API client. The CLI echoes it verbatim, so following the advice returns the operator to the same message. The carried fallback said "Run ocx stop" as well, which made the empty-body path a tighter loop than the one being fixed.

So the refusal code now travels on the error, the fallback messages name the cause only, and a new refusalNextStep supplies the command, printed by the CLI beneath the refusal. The only callers of stopProxy are ocx stop and the service manager's own cleanup, and by the time either reaches this point the service manager has already been asked to stop — so no branch answers with the command that just failed. Every one points at ocx service status, which is what actually reports the wrapper state.

What the operator sees now, for a respawnable_service refusal that arrives with the server's message:

❌ Failed to stop proxy (PID 1234).
   This proxy is managed by a Task Scheduler wrapper that can respawn it, so the stop must be run by `ocx stop`, which verifies the respawn window. Nothing was changed.
   This stop already asked the service manager to stop, so running `ocx stop` again is not the missing step. Run `ocx service status` to see whether a wrapper is still installed and able to respawn the proxy.
   Skipping shared teardown (native Codex restore, Grok config): the refusing proxy is still running.

ProxyOwnershipRefusedError keeps its name. The issue suggests renaming it and the name does overstate what it now carries, but @yeongjunyoo deliberately left that out as a separate wider change and it is not part of the issue's Expected behaviour. Recorded here rather than decided unilaterally.

Verification

The local product suite, typecheck and GUI build were NOT RUN, by operator instruction for this dispatch round. bun test, bun run test, bun run test:changed, bun run typecheck, bun run build:gui and bun install were all NOT RUN. Hosted CI on this exact pushed head is the product evidence for this PR.

What was checked instead, by read-only review agents reading the source and by deterministic text checks:

  • The loop was confirmed from code before it was fixed: ocx stop does reach POST /api/stop, the message-present path was untouched by the carry, and there is no header, flag or route that lets the refusal printer tell a CLI-originated stop from an API client — which is why the correction belongs in the CLI, the one caller that knows which it is.
  • An adversarial review of the staged diff found a real defect and it was fixed: the new next-step test banned the literal `ocx stop`, which its own production wording contains in order to rule it out. The assertion now bans a recommendation (/Run \ocx stop`/`) rather than a mention.
  • Every source-oracle assertion in tests/providers/xai/grok-lifecycle.test.ts that reads these two files as text was re-counted against the current source: the ProxyOwnershipRefusedError/ownershipBlocked pairs, the teardown-skip lines, and the detail echoes are all still exactly 2, and the new refusalNextStep(err.code) print sites are 2.
  • No test anywhere still asserts the removed fallback wording, and the similar strings in src/server/management-api.ts are untouched — the server's own messages are not changed by this PR.
  • The CLI wiring is now pinned by a source oracle; the review noted it was previously unasserted.

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.

This targets codex/260911-l4-service-cli (PR #4235) as a stacked child and will be retargeted to dev once the parent lands. No hunks were dropped from #4170: all four of its files are inside this lane's ownership.

Closes #4169

Co-authored-by: yeongjunyoo 47925973+yeongjunyoo@users.noreply.github.com

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 10, 2026 23:20
@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 2cc1909d-e587-4aaa-a2cd-f1d9d2890247

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 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-10T23:24:39.486803Z 022d46a 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 bug Something isn't working label Sep 10, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 69 / 80

이 PR은 지금 dev(HEAD df65a2587, 패키지 2.51.0) 위에서 돌아가는 L4 서비스/CLI 레인의 다음 조각입니다. 부모는 #4235(codex/260911-l4-service-cli, pnpm 전역 자가 업데이트 WP1)이고, 이 PR은 그 브랜치에 쌓인 자식(codex/260911-l4-stop-refusal)입니다. 부모가 dev에 들어가기 전에는 이 PR도 dev에 바로 못 붙습니다. 고치는 문제는 #4169입니다. 지금 devstopProxyPOST /api/stop이 409로 거절해도, 본문이 비면 항상 “다른 CODEX_HOME/OPENCODEX_HOME이 소유한다”고 말합니다. 서버는 사실 세 가지 코드(respawnable_service, self_unload_service, service_state_unknown)를 구분하는데, CLI가 그걸 한 문장으로 뭉갭니다. 제보자는 집이 정확히 같았고 서버 답은 respawnable_service였는데도, 안내만 보고 CODEX_HOME을 세 번이나 다시 내보냈습니다.

더 나쁜 건 그 다음 조언이 루프라는 점입니다. 서버 거절 문구는 API 클라이언트를 향해 “ocx stop으로 하라”고 쓰고, ocx stop은 그 문구를 그대로 찍습니다. 운영자가 그 조언을 따르면 같은 화면으로 돌아옵니다. 기여자 PR #4170이 거절 code를 붙이고 폴백 문구를 코드별로 고르는 쪽을 이미 열었지만, #4169 Expected의 둘째 절반(방금 실패한 명령을 다시 시키지 말 것)은 열려 있었습니다. 이 랜딩은 #4170을 L4 소유지로 캐리하면서 그 둘째 절반까지 닫습니다. stopProxyGracefullyDetailed가 시도마다 message/code를 돌려주고, ProxyOwnershipRefusedErrorcode가 실립니다. 빈 본문이면 refusalFallbackMessage가 원인만 말하고, CLI는 refusalNextStep(err.code)이미 물어본 서비스 매니저 상태를 보라고 ocx service status를 찍습니다. 테ardown 스킵 문구도 “foreign”에서 “refusing”으로 바뀌어, 소유권 오해를 한 번 더 줄입니다. 테스트는 코드별 폴백·동시 거절·“Run ocx stop” 추천 금지·소스 오라클이 tests/lib/process-control-graceful.test.tstests/providers/xai/grok-lifecycle.test.ts에 있습니다. 로컬 제품 스위트는 돌리지 않았고, 증거는 호스티드 CI로 두었습니다. Closes #4169가 본문에 있습니다.

src/lib/process-control.ts ProxyOwnershipRefusedError - 클래스 이름이 여전히 “Ownership”이라, 코드가 respawnable_service여도 타입만 보면 소유권 거절로 읽힙니다. PR이 의도적으로 이름 변경을 빼 두었고 #4169 Expected에도 없으므로 이번 범위 밖이 맞지만, 나중에 읽는 사람을 위해 follow-up 이슈로 남길지 정하면 좋습니다.

src/lib/process-control.ts lastRefusalCode / lastStopRefusalCode - 거절 본문은 시도별 refusal로 고쳤는데, 관찰용 모듈 전역은 그대로 갱신됩니다. 동시 stop이 겹치면 전역 accessor는 여전히 마지막 쓴 쪽을 보여 줍니다. 에러 throw 경로는 안전하고, 전역을 읽는 호출자가 있다면 그 한계를 문서에 한 줄 더 박아 두는 편이 낫습니다.

베이스 codex/260911-l4-service-cli (#4235) - 이 PR은 dev 직행이 아닙니다. 부모 CI(런처 정책 문자열 등)가 아직 정리 중이면 자식도 같이 밀립니다. 머지 순서는 부모 → 이 PR → dev 리타깃(또는 스택 머지)입니다.

#4170 - 같은 제목·같은 축의 원본이 아직 dev 대상으로 열려 있습니다. 이 스택이 랜딩되면 원본은 Landed via #4237 + landed-via-maintainer로 닫는 패턴이 맞습니다. 둘을 같이 두면 기여자 큐가 또 부풀어 보입니다.

메인테이너의 판단이 필요한 지점

  • ProxyOwnershipRefusedError 이름 변경을 이번 레인 follow-up으로 받을지, 별도 이슈로 미룰지
  • fix(update): support native pnpm self-updates #4235 부모 CI가 초록이 된 뒤에만 이 자식을 머지할지(스택 규율)
  • #4170을 이 PR 머지 직후 즉시 닫을지

너의 추천
부모 #4235 CI·런처 테스트 문자열을 먼저 초록으로 만든 뒤 이 PR을 스택으로 머지하세요. 머지 커밋이 dev에 안착하면 #4169는 Closes로 닫히게 두고, #4170에는 Landed via #4237 at <sha> + landed-via-maintainer를 달고 닫으세요. 클래스 이름 변경은 이번 범위 밖이니 막지 말고, 원하면 짧은 follow-up만 남기면 됩니다.

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

POST /api/stop refuses for three distinct reasons - respawnable_service,
self_unload_service and service_state_unknown - but when no readable message
arrived, stopProxy fell back to a single hardcoded sentence naming a fourth
cause the server never reports: a CODEX_HOME/OPENCODEX_HOME ownership
mismatch. The reporter's homes matched exactly and the server had answered
respawnable_service, so three attempts went into re-exporting CODEX_HOME.

Carried from #4170 by @yeongjunyoo: the refusal code is captured next to the
message, the fallback wording is selected from that code, and the refusal is
returned per attempt rather than published to module state, so two overlapping
stops cannot lend each other the wrong cause.

This also closes the second half of the issue, which the carry left open. #4169
asks that "the recommended next command should not be the command the operator
just ran", and it is still is: ocx stop reaches POST /api/stop through
handleStop -> stopWithDeferral -> stopProxy, and the server answers "the stop
must be run by `ocx stop`" because that refusal is written for an API client.
The CLI echoes it verbatim, so following the advice returns the operator to the
same message. The carried fallback said "Run `ocx stop`" too, making the
empty-body path a tighter loop than before.

The refusal code now travels on the error, the fallback messages name the cause
only, and refusalNextStep supplies the command. The only callers of stopProxy
are ocx stop and the service manager's own cleanup, and by the time either
reaches this point the service manager has already been asked to stop, so no
branch answers with the command that just failed - every one points at
`ocx service status`, which is what actually reports the wrapper.

ProxyOwnershipRefusedError keeps its name. #4169 suggests renaming it and the
name does overstate what it carries, but the carrying author deliberately left
that out as a separate wider change and it is not part of the issue's expected
behaviour.

Closes #4169

Co-authored-by: yeongjunyoo <47925973+yeongjunyoo@users.noreply.github.com>
@lidge-jun
lidge-jun force-pushed the codex/260911-l4-stop-refusal branch from 022d46a to 1ada8f5 Compare September 10, 2026 23:23

@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: 022d46a3ec

ℹ️ 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 thread src/cli/index.ts
// recommends `ocx stop` — the command printing it. Following that advice returns
// the operator to this exact message, which is the loop #4169 was filed for. The
// service manager was already asked to stop above, so name what is actually left.
console.error(` ${refusalNextStep(err.code)}`);

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 Suppress the circular stop recommendation

When POST /api/stop returns its normal respawnable_service or self_unload_service body, err.message is printed immediately above this line, and those server messages explicitly say to run ocx stop (src/server/management-api.ts:300 and :314). Appending a second sentence that contradicts that recommendation still leaves the operator being told to rerun the command that just failed, so the intended loop remains. For these known refusal codes, replace or sanitize the API-oriented message before printing it rather than merely adding another next step; apply the same change to the orphan-recovery catch.

Useful? React with 👍 / 👎.

Base automatically changed from codex/260911-l4-service-cli to dev September 11, 2026 00:37
@lidge-jun
lidge-jun merged commit bb17773 into dev Sep 11, 2026
31 checks passed
@lidge-jun
lidge-jun deleted the codex/260911-l4-stop-refusal branch September 11, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant