Skip to content

Support Claude event-array results and fail-closed Grok API-key setup [#58] - #130

Merged
mergify[bot] merged 5 commits into
mainfrom
factory/issue-58
Oct 4, 2026
Merged

mergify[bot] merged 5 commits into
mainfrom
factory/issue-58

Conversation

@mast-factory

@mast-factory mast-factory Bot commented Oct 4, 2026

Copy link
Copy Markdown

Summary

Fixes #58 by supporting current Claude JSON event-array results and Grok API-key authentication without weakening model availability checks.

  • Select the last Claude type: "result" event, reject terminal errors before accepting text, and preserve legacy object parsing plus model/session/usage/cost metadata.
  • Accept Grok subscription or You are using XAI_API_KEY. authentication only with bounded exact-model evidence and no explicit authentication or unsupported-model failure. Rejected models exit 69 without launching; authentication failures retain the bounded retry and exit 77.
  • Exercise Preserve Grok terminal failures and complete runner evidence [#78] #126's existing typed terminal failures, nonzero exits, metadata, and stdout/stderr sidecar assertions under both subscription and API-key preflight. Preserve Explain Codex sandbox restrictions before pstack setup probes [#124] #129's sandbox diagnostics.

Dependencies and scope

Merged factory/issue-124 at e257bad2c7567bd5108c5c654ac27dc920efa5cc into factory/issue-58, without rebase or force-push. This includes #126 and #129. Do not land this PR before both dependencies. They remain open at publication; after they land, merge updated origin/main into this branch and repeat the affected gates.

Relative to that dependency base, implementation changes are limited to runner/parse-output.ts, runner/parse-output.test.ts, runner/run.ts, and runner/run.test.ts. No setup-contract or stdin transport changes.

#94 stdin: not reproducible on available hosts (Bun 1.4.0, macOS, Codex sandboxes); left open

The approved revision-11 verdict removes the proposed stdin transport phase. runProcess is unchanged, and this PR does not claim to fix #94.

Test plan and actual results

Run with Bun 1.4.0 against pushed candidate 277029a694c0077532400e97f1ae7e1293c22b8c. Automated gates completed before opening this PR.

Command Exit Actual output
Pre-fix bun test runner/parse-output.test.ts runner/run.test.ts on merged prerequisite base 1, expected 76 pass, 11 fail, 411 assertions; reproduced array parsing, API-key authentication, contradictory failure, and neighboring-token defects
bun test runner/parse-output.test.ts 0 14 pass, 38 assertions
bun test runner/run.test.ts runner/parse-output.test.ts 0 92 pass, 0 fail, 557 assertions
bun run test 0 214 pass, 0 fail, 1,075 assertions across 11 files
bun run typecheck 0 Strict no-emit checks for watch-pr, runner, and check-plan
bun -e 'for (const path of [".claude-plugin/marketplace.json", ".agents/plugins/marketplace.json", "plugins/pstack/.claude-plugin/plugin.json", "plugins/pstack/.codex-plugin/plugin.json"]) JSON.parse(await Bun.file(path).text())' 0 All four manifests parsed
PSTACK_STATIC_ONLY=1 bash tests/skill-collision-repro.sh 0 All 13 static checks passed
git diff --check and dependency-base range check 0 No whitespace errors

Retained logs: /tmp/open-pstack-issue-58/{pre-fix-tests,claude-tests,focused-tests,full-tests,typecheck,manifests,static-collision}.log in the build sandbox. No live credentials were used by repository tests.

Remaining acceptance evidence — after review

Per approval, live verification is deferred, not claimed complete:

  • Real Codex-parent external Claude lane producing a JSON event array.
  • Real Grok lane authenticated by XAI_API_KEY; blocked unless the operator provides a key through the runtime environment, never the repository.
  • Installed setup/readback and mixed-panel evidence from both Codex and Claude parents, with external receipts, native transcripts, and independent cross-judge verification.

Pushed phase checkpoints

  • Dependency merge: 9a2dfd0f9ac4f40ec44a59b63d31aec484ba0a04.
  • Claude parser: 20af5b22e359a622b60b2702521b6e0acf3b7d96.
  • Grok preflight / verified HEAD: 277029a694c0077532400e97f1ae7e1293c22b8c.

Findings

None

Build the combined setup fix on the approved #126/#129 dependency history.

Co-Authored-By: mastra-platform[bot] <284800079+mastra-platform[bot]@users.noreply.github.com>
Select the last terminal result while preserving legacy objects and terminal metadata. Reject errors before requiring final text.

Co-Authored-By: mastra-platform[bot] <284800079+mastra-platform[bot]@users.noreply.github.com>
Require bounded model availability and reject explicit authentication or unsupported-model failures before execution; verify compatibility with typed terminal failures and sidecars.

Co-Authored-By: mastra-platform[bot] <284800079+mastra-platform[bot]@users.noreply.github.com>
@mergify

mergify Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 2 merge protections satisfied — ready to merge.

Show 2 satisfied protections

🟢 Queue ready pull requests

Automatically queue non-draft pull requests targeting main after verify, Unfret, and live-gate pass on the head.

  • -draft
  • check-success=Unfret
  • check-success=live-gate
  • check-success=verify

🟢 🚦 Auto-queue

When all merge protections are satisfied and these conditions match, this pull request will be queued automatically.

  • -files~=^\.github/
  • base = main

@greptile-apps

greptile-apps Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Extends Claude output parsing and Grok setup validation.

This PR should wait until Grok preflight stops treating informational authentication text as a failed login.

Findings

  1. P1 Valid Grok key gets rejected ▶

Summary

The runner now reads Claude event-array results, accepts Grok API-key authentication only with evidence for the requested model, and keeps more detail about Grok runs in receipts. Setup guidance also adds parent-permission checks and explains when Codex sandbox limits affect external lanes.

  • Claude parsing selects the last terminal result and preserves its run details.
  • Grok preflight checks authentication and exact-model evidence before execution.
  • Grok receipts retain provider failures and raw stdout/stderr; Codex network-disabled failures get a likely-sandbox hint.
  • Setup instructions check external access and both configuration destinations before proceeding.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Reserve result paths] --> B[Run provider preflight]
  B -->|Passed| C[Run model and save streams]
  B -->|Failed| F[Write dropout receipt]
  C --> D[Parse final result]
  D -->|Valid model proof| E[Write answer and receipt]
  D -->|Failure| F
Loading

Reviews (1) · Last reviewed commit: "fix(#58): accept Grok API keys without b..."

Comment on lines +412 to +414
return unavailableStatus(combined) === "child-failed"
&& /\blogged in\b|\bYou are using XAI_API_KEY\./i.test(combined)
&& grokModelAvailable(combined, model);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Valid Grok key gets rejected

If grok models prints an informational line such as Authentication: API key alongside You are using XAI_API_KEY. and the requested model, this new check rejects the response. unavailableStatus treats the word authentication alone as a failed login. The runner then retries and exits 77 without starting the lane. Check for a stated authentication failure instead of any mention of authentication.

Knowledge Base Used: Poteto runner and orchestration

@unfret-eal

unfret-eal Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

✅ Accepted risk

Accepted by ericlitman: GH-58 (authority 48fa77b)

🟡 Medium · Malformed-result test inherits the parent sandbox marker

🟡 Medium · Long rejected Grok preflights get the wrong status

Advisories (1)
  • Claude-reported failures receive a misleading sandbox hint
    Not blocking because: The hint misidentifies a likely cause without changing failure status, launching another lane, or altering permissions. Complete provider output remains in private sidecars, bounding the confirmed consequence to diagnostic guidance.
Details

Run: GET /unfret/run/panel:5d758660255a26536fff3f05f205e4be205cf892:cabQFX6nqNT:UW01WDFjJGg.

@unfret review · @unfret status · @unfret help

@unfret-eal unfret-eal 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.

🛑 2 blocking findings
🟡 Medium · Isolate the parent network marker in the malformed-result test
🟡 Medium · Long rejected Grok preflights receive the wrong status
Check

status: exitCode === 0 ? "malformed-output" : "child-failed",
exitCode, reportedModel: null, modelVerified: false,
sessionId: null, error: { message: exitCode === 0
? "grok result did not contain a valid terminal status" : "child exited with status 1" },

@unfret-eal unfret-eal Bot Oct 4, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Malformed-result test inherits the parent sandbox marker

The exit-1 assertion expects exactly child exited with status 1, but options("grok") selects a Codex parent and the file-level hooks do not clear CODEX_SANDBOX_NETWORK_DISABLED. When the suite runs from a supported Codex workspace-write session with the marker set to 1, the documented sandbox hint is appended and this test fails; reproduced with CODEX_SANDBOX_NETWORK_DISABLED=1 bun test .../run.test.ts (84 pass, 1 fail). Also reported for this defect: - Ambient sandbox marker breaks the Bun test suite (plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts:638): When the suite runs from a Codex workspace with CODEX_SANDBOX_NETWORK_DISABLED=1, this malformed-result test invokes runLane in the same process and expects exactly "child exited with status 1". The marker is cleared only inside the separate diagnostics describe block, so the runner appends the new sandbox hint and this assertion fails, breaking the required bun run test gate. - Isolate the parent network marker in the malformed-result test (plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts:638, plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts:637-638): With CODEX_SANDBOX_NETWORK_DISABLED=1, the exit-1 case correctly receives the documented sandbox hint, but this assertion requires the unhinted message. The marker is cleared only inside the separate diagnostics describe block, so this test inherits it from a restricted Codex parent. Reproduced with CODEX_SANDBOX_NETWORK_DISABLED=1 bun run test: 213 pass, 1 fail. Clear and restore the marker for this test's unhinted scenario. Also reported for this defect: - Test expects no sandbox hint but inherits the ambient marker (plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts:637-638): The new hint in run.ts reads the runner process's own CODEX_SANDBOX_NETWORK_DISABLED, and the tests call runLane in the same process. Only the "Codex parent sandbox diagnostics" describe clears that variable; the file-level beforeEach/afterEach do not. The test "keeps malformed terminal data distinct after child exit 1" uses options("grok") and requires error.message to equal exactly "child exited with status 1". That path is a plain child-failed with no providerFailure, so the hint applies. Trigger: bun test runs from a default Codex workspace-write session, which the diff's own doc says exports CODEX_SANDBOX_NETWORK_DISABLED=1. AGENTS.md also requires agents to run Bun tests before opening a PR. If options() gives grok a codex parent, the message becomes "child exited with status 1; likely cause: Codex parent sandbox…" and the assertion fails, breaking the pre-PR test gate. The codex default is likely: the claude test at line 326 gets the hint without overriding the parent. However, the options() helper is outside the visible lines, so the grok default cannot be confirmed.

Now at plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts:638-639

Also affects:

Comment on lines +412 to +414
return unavailableStatus(combined) === "child-failed"
&& /\blogged in\b|\bYou are using XAI_API_KEY\./i.test(combined)
&& grokModelAvailable(combined, model);

@unfret-eal unfret-eal Bot Oct 4, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Long rejected Grok preflights get the wrong status

This veto examines complete stdout/stderr, but retry and final classification later receive rawPreflightEvidence truncated to 4,000 characters. If an authenticated exit-0 preflight lists the requested model, emits over 4,000 characters, then reports model grok-4.6 is not supported on stderr, the veto rejects it while classification loses the reason, waits five seconds, retries, and returns unauthenticated/77 instead of unavailable-model/69; this was reproduced on the current head. Also reported for this defect: - Long rejected Grok preflights get the wrong status (plugins/pstack/skills/poteto-mode/scripts/runner/run.ts:412): The new veto examines the complete stdout/stderr, but downstream retry and failure classification still use evidence truncated to 4,000 characters. If an exit-0 preflight prints authentication and the requested model early, more than 4,000 characters of output, then an unsupported-model reason on stderr, this check rejects it while classification misses the reason; the runner unnecessarily retries and returns unauthenticated/77 instead of unavailable-model/69. - Rejected Grok preflight misclassified as unauthenticated (plugins/pstack/skills/poteto-mode/scripts/runner/run.ts:412-414): Carried demand, keep-confirmed. The run.ts hunks are unchanged since the earlier reproduction, and nothing visible passes full streams to classification. The new veto at lines 412-414 scans the complete stdout and stderr. Retry selection and preflightFailureStatus (line 440) still classify the preflight evidence after it is truncated to 4,000 characters. That call site is outside the diff, so I cannot see it directly. Failure scenario: - An exit-0 grok models run prints You are logged in.\nAvailable models: grok-4.6 followed by more than 4,000 characters on stdout. - Stderr then reports model grok-4.6 is not supported. - The veto rejects the preflight. - The truncated evidence lacks the rejection but still contains the exact model token, so classification falls through to unauthenticated. - The runner waits through the 5-second retry and returns 77, not the unavailable-model/69 the PR promises. Same root cause at line 440: if truncation cuts grok-4.6-build right after grok-4.6, the token check passes on the evidence while the full-stream check failed. This also yields a retry and 77. - Long rejected Grok preflights receive the wrong status (plugins/pstack/skills/poteto-mode/scripts/runner/run.ts:412-414): The new veto examines complete stdout/stderr, but retry and final failure classification still receive evidence truncated to 4,000 characters. When an authenticated exit-0 preflight lists the requested model, emits more than 4,000 characters on stdout, then reports model grok-4.6 is not supported on stderr, the veto rejects it while classification loses the rejection reason. Reproduced under both authentication modes: the runner retries and returns unauthenticated/77 instead of unavailable-model/69. Classification needs the full streams, independently of receipt truncation.

Now at plugins/pstack/skills/poteto-mode/scripts/runner/run.ts:412-414

Also affects:

@ericlitman

Copy link
Copy Markdown
Owner

Unfret waiver for head 277029a. Ticket GH-58 is GitHub issue #58.

Finding Class Basis
v1:b23d4d15, malformed-result test inherits CODEX_SANDBOX_NETWORK_DISABLED fails-loudly Inherited from #129, waived there as fails-loudly at e257bad and noted in that PR body. One exact-message assertion fails only when the suite runs inside a network-disabled Codex session. Runtime behavior is correct, and CI and unsandboxed runs pass. The fix (clear the marker in that block) goes with the next change to the file.
v1:be3dae7c, a long rejected Grok preflight gets the wrong status pre-accepted Trigger: grok models exits 0, prints more than 4,000 characters on stdout, then a "not supported" line on stderr. Real grok models output is a short list of a few hundred characters, so this needs the CLI to behave outside its observed contract. Consequence: the run still fails closed before any model launch, with no wrong success and no fallback. Only the label (unauthenticated/77 instead of unavailable-model/69) and one bounded retry differ. The fix would add special-case evidence plumbing.

Neither finding involves security, secret exposure, data loss, or gate or waiver integrity.

@ericlitman

Copy link
Copy Markdown
Owner

Live test, partial (head 277029a)

Result: a Codex regression check passed, but #58's two headline behaviors have not run on the real surface. I have not set the live-gate status.

  • Installed version: Open Pstack 1.5.0 from ref 277029a (full SHA in the marketplace config). I installed it into a fresh, throwaway CODEX_HOME with Codex CLI 0.160.0. SHA-256 of the installed run.ts and parse-output.ts matches the candidate.
  • Surface: the interactive Codex TUI (GPT-6.1 Sol).
  • Action, regression check of the changed Grok preflight with subscription login: one Grok lane, --parent codex --provider grok --model grok-4.7 --effort low --mode read-only, with the prompt "Reply with only the word cobalt."
  • Observed:
    • Preflight passed, with first line "authenticated; model grok-4.7 available".
    • reportedModel grok-4.7-build, modelVerified true.
    • Output cobalt.

Blocked, still required before live-gate:

  1. Claude JSON event-array output, through a real external Claude lane. It needs a logged-in Claude Code CLI on the test host. claude auth status reports loggedIn: false.
  2. Grok XAI_API_KEY preflight and a one-turn lane. It needs the operator to supply an API key. The host is logged in with grok.com.
  3. The Claude Code parent run.

Review state: Unfret shows Accepted risk on this head. The test-isolation finding is fails-loudly, and the long-preflight label finding is pre-accepted. The waiver is in the comments.

Co-Authored-By: mastra-platform[bot] <284800079+mastra-platform[bot]@users.noreply.github.com>

@unfret-eal unfret-eal 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.

🛑 2 blocking findings
🟡 Medium · Malformed-result test inherits the parent sandbox marker
🟡 Medium · Long rejected Grok preflights get the wrong status
Check

@ericlitman

Copy link
Copy Markdown
Owner

Waiver re-applied for head 5d75866 (queue update of 277029a).

Unfret waiver for head 277029a. Ticket GH-58 is GitHub issue #58.

Finding Class Basis
v1:b23d4d15, malformed-result test inherits CODEX_SANDBOX_NETWORK_DISABLED fails-loudly Inherited from #129, waived there as fails-loudly at e257bad and noted in that PR body. One exact-message assertion fails only when the suite runs inside a network-disabled Codex session. Runtime behavior is correct, and CI and unsandboxed runs pass. The fix (clear the marker in that block) goes with the next change to the file.
v1:be3dae7c, a long rejected Grok preflight gets the wrong status pre-accepted Trigger: grok models exits 0, prints more than 4,000 characters on stdout, then a "not supported" line on stderr. Real grok models output is a short list of a few hundred characters, so this needs the CLI to behave outside its observed contract. Consequence: the run still fails closed before any model launch, with no wrong success and no fallback. Only the label (unauthenticated/77 instead of unavailable-model/69) and one bounded retry differ. The fix would add special-case evidence plumbing.

Neither finding involves security, secret exposure, data loss, or gate or waiver integrity.

@ericlitman

Copy link
Copy Markdown
Owner

Live tests and live-gate (head 5d75866)

This head is level with main (after #126, #113 and #129), and Unfret shows Accepted risk. I ran both harnesses on this exact head. All lanes pass, and I set live-gate to success on 5d758660255a26536fff3f05f205e4be205cf892.

  • Installed version:
    • Codex CLI 0.160.0, with the candidate installed from --ref 5d758660255a26536fff3f05f205e4be205cf892 into a fresh CODEX_HOME. Its auth.json is a symlink to the operator's login. Installed run.ts and parse-output.ts match the candidate byte for byte.
    • Claude Code 2.1.289, with the candidate loaded via --plugin-dir from a checkout of exactly 5d758660255a26536fff3f05f205e4be205cf892 and the installed pstack turned off.
  • Surface: codex exec --sandbox danger-full-access and claude -p sessions, each running one exact launcher command through its shell tool. Every lane got the prompt "Reply with only the word cobalt.", ran --effort low --mode read-only, and answered cobalt.
Parent Lane Auth Status Model evidence
Codex external Claude, opus operator's Claude login complete, exit 0 claude-opus-5-5, verified from the provider report
Codex external Grok, grok-4.7 XAI_API_KEY in the parent environment complete, exit 0. Preflight: "authenticated; model grok-4.7 available" grok-4.7-build, verified
Claude Code external Grok, grok-4.7 XAI_API_KEY in the parent environment complete, exit 0. Preflight passed grok-4.7-build, verified
Claude Code external Codex, gpt-5.6-sol operator's Codex login complete, exit 0 not reported by Codex (existing behaviour, unchanged here)
  • With that key, grok models prints "You are using XAI_API_KEY." The pre-Fix Codex setup with Claude JSON event arrays and Grok API-key authentication #58 runner rejected that preflight as unauthenticated; I saw this on the 1.5.0 runner on 2026-10-04. This candidate accepts it, because the exact model is listed and there is no explicit failure.
  • Claude JSON event arrays: Claude Code 2.1.289 prints a single result object under the runner's --output-format json flags. It emits the event-array form only with --verbose. So the real-surface Claude lane covered the legacy-object path, and the array path is covered by the regression tests. The array parsing is unchanged from the reviewed implementation.
  • Fix runner subprocess I/O for Codex provider probes #94 stdin: not reproduced on the available hosts, and left open as approved.

@mergify

mergify Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • ✅ Entered queue — 2026-10-04 13:22 UTC · Rule: default · triggered by merge protections
  • ✅ Checks passed · in-place
  • ✅ Merged — 2026-10-04 13:27 UTC · at c3a177dccc2c54f0fe51caadde20ca269fb008d0 · merge

This pull request spent 4 minutes 55 seconds in the queue, including 4 minutes 42 seconds running CI.

Required conditions to merge

@mergify mergify Bot added the queued label Oct 4, 2026
@ericlitman

Copy link
Copy Markdown
Owner

Live tests and live-gate for the queue-created head 927a7b6

Mergify merged main (#119, a one-line .github/unfret.json change) into this branch, creating 927a7b63bd19e75b78b046b8b8a8836223a859a9. I re-ran all four lanes on this exact head with the same method as the run on 5d75866. All pass, and I set live-gate to success on 927a7b63bd19e75b78b046b8b8a8836223a859a9.

Parent Lane Status Model evidence Output
Codex (codex exec) external Claude opus, operator login complete, exit 0 claude-opus-5-5, verified cobalt
Codex external Grok, XAI_API_KEY in env complete, exit 0. Preflight "authenticated; model grok-4.7 available" grok-4.7-build, verified cobalt
Claude Code (claude -p, candidate via --plugin-dir) external Grok, XAI_API_KEY in env complete, exit 0 grok-4.7-build, verified cobalt
Claude Code external Codex gpt-5.6-sol complete, exit 0 not reported by Codex (unchanged behaviour) cobalt

The installed run.ts and parse-output.ts match the candidate byte for byte.

@mergify
mergify Bot merged commit c3a177d into main Oct 4, 2026
5 checks passed
@mergify mergify Bot removed the queued label Oct 4, 2026
andrewfree pushed a commit to andrewfree/open-pstack that referenced this pull request Oct 4, 2026
Bring in ericlitman/open-pstack main at c3a177d: Claude terminal event
arrays and Grok API-key preflight (ericlitman#58). Grok preflight keeps the local
Kimi effort and Cursor listed-model checks beside upstream's whole-token
model match. Version 1.5.1-local.8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix runner subprocess I/O for Codex provider probes Fix Codex setup with Claude JSON event arrays and Grok API-key authentication

1 participant