-
Notifications
You must be signed in to change notification settings - Fork 27
release: v1.4.2 #93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
release: v1.4.2 #93
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /** | ||
| * Copyright 2026 Sendbird, Inc. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| import process from "node:process"; | ||
|
|
||
| /** | ||
| * Detect whether this Codex session is hosted by an external assistant rather | ||
| * than a user-driven Codex frontend. A Codex app-server spawned from inside | ||
| * Claude Code inherits the Claude Code process env (measured: CLAUDECODE=1 / | ||
| * CLAUDE_CODE_ENTRYPOINT reach plugin hooks). Threads in such an app-server | ||
| * are host-driven, so companion delegation must not loop back to Claude Code. | ||
| * | ||
| * Every writer of the current-session marker must stamp this, or a later | ||
| * rewrite would erase the origin and reopen the delegation loop. | ||
| */ | ||
| export function detectExternalHostOrigin() { | ||
| if (process.env.CLAUDECODE || process.env.CLAUDE_CODE_ENTRYPOINT) { | ||
| return "claude-code"; | ||
| } | ||
| return null; | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -78,6 +78,7 @@ import { | |
| generateJobId, | ||
| getConfig, | ||
| getCurrentSession, | ||
| getCurrentSessionMarker, | ||
| listJobs, | ||
| patchJob, | ||
| JOB_RESERVATION_SUFFIX, | ||
|
|
@@ -246,6 +247,36 @@ function alignCurrentSessionToOwner(workspaceRoot, ownerSessionId) { | |
| setCurrentSession(workspaceRoot, ownerSessionId); | ||
| } | ||
|
|
||
| /** | ||
| * Refuse delegation from a Codex thread that is itself driven by an external | ||
| * host (e.g. a headless review thread spawned by Claude Code). Delegating back | ||
| * to Claude Code from there loops the work between the two assistants. | ||
| * | ||
| * Interactive Codex sessions receive SESSION_ID_ENV through the session hook's | ||
| * env-file export; externally hosted app-server threads do not (measured), so | ||
| * an absent env session id plus a `hostOrigin` stamp on the current-session | ||
| * marker identifies the loop. Fail-open everywhere else. | ||
| */ | ||
| function assertDelegationAllowed(workspaceRoot, ownerSessionId, workLabel) { | ||
| if (process.env[SESSION_ID_ENV]) { | ||
| return; | ||
| } | ||
| const marker = getCurrentSessionMarker(workspaceRoot); | ||
| if (!marker || marker.hostOrigin !== "claude-code") { | ||
| return; | ||
|
Comment on lines
+264
to
+266
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a Claude-driven thread and a normal Codex session are open in the same workspace, both hooks overwrite the same workspace-global AGENTS.md reference: AGENTS.md:L4-L4 Useful? React with 👍 / 👎. |
||
| } | ||
| if (ownerSessionId && ownerSessionId !== marker.sessionId) { | ||
| return; | ||
| } | ||
| throw new Error( | ||
| [ | ||
| `This Codex thread is driven by Claude Code, not by a user prompt, so delegating this ${workLabel} back to Claude Code would loop it between the two assistants.`, | ||
| `Do not retry this command and do not look for another way to reach Claude Code.`, | ||
| `Perform the requested ${workLabel} yourself in this thread and present your own findings directly.`, | ||
| ].join("\n") | ||
| ); | ||
| } | ||
|
|
||
| async function withReleasedReservation(workspaceRoot, explicitJobId, fn) { | ||
| try { | ||
| return await fn(); | ||
|
|
@@ -1515,6 +1546,7 @@ async function handleReviewCommand(argv, config) { | |
| await withReleasedReservation(workspaceRoot, explicitJobId, async () => { | ||
| // Validate inside the reservation guard so failures do not leak markers. | ||
| config.validateRequest?.(target, focusText); | ||
| assertDelegationAllowed(workspaceRoot, ownerSessionId, "review"); | ||
| const metadata = buildReviewJobMetadata(config.reviewName, target); | ||
| alignCurrentSessionToOwner(workspaceRoot, ownerSessionId); | ||
|
|
||
|
|
@@ -1634,6 +1666,7 @@ async function handleTask(argv) { | |
| const ownerSessionId = resolveOwnerSessionId(options["owner-session-id"]); | ||
| const explicitJobId = resolveExplicitJobId(options["job-id"], workspaceRoot); | ||
| await withReleasedReservation(workspaceRoot, explicitJobId, async () => { | ||
| assertDelegationAllowed(workspaceRoot, ownerSessionId, "task"); | ||
| const taskMetadata = buildTaskRunMetadata({ | ||
| prompt, | ||
| resumeLast | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
UserPromptSubmitran butcaptureTurnBaseline()failed—for example because fingerprinting encountered a transient Git/filesystem error—the hook catches that error and leaves no baseline. This branch now treats the missing baseline as proof that no prompt occurred and silently skips an enabled turn-end review, whereas the previous behavior ran the review in this case. Distinguish an explicitly headless session from a baseline-capture failure instead of failing open for every absent or unreadable baseline.Useful? React with 👍 / 👎.