Skip to content

fix(antigravity): bound POSIX hook stdin so agy cannot hang loading - #15821

Closed
brennanb2025 wants to merge 2 commits into
mainfrom
brennanb2025/sta-4700-agy-loading
Closed

brennanb2025 wants to merge 2 commits into
mainfrom
brennanb2025/sta-4700-agy-loading

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​338 $\color{#cf222e}{\Huge{\mathbf{−}}}$​11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​327
Prod 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​51 $\color{#cf222e}{\Huge{\mathbf{−}}}$​10 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​41

STA-4700

Problem

Antigravity (agy) waits for each managed hook process to exit before continuing its execution loop. The POSIX antigravity-hook.sh captured stdin with unbounded cat. When the agent omits stdin or leaves the pipe open until the hook exits, cat never sees EOF, the hook never reaches curl/exit 0, and both the CLI and Orca’s sidebar stay in a loading/working state.

Reproduction

Spawn the generated POSIX script with Orca env present and leave stdin open (do not send EOF). Pre-fix: the process hung until killed (timedOut: true at ~4s). Closed-and-written stdin still completed in ~0.3s and posted the payload.

A live agy --print from a throwaway HOME hit Google OAuth, so causation from the live TUI is inferred from this hang plus the documented no-stdin / abandoned-pipe cases (#15117, #11549). The hook-process hang itself was observed on the installed script.

Fix

In src/main/antigravity/hook-script.ts only (shared hook-stdin-contract.ts unchanged):

  1. Print the gate JSON first (ask on PreToolUse, empty decision on Stop).
  2. Exit without reading stdin when Orca env is missing.
  3. Otherwise capture stdin with a watchdog-killed cat (fd 3 so a background cat does not inherit /dev/null; command -p sleep so empty PATH cannot fire the kill immediately).
  4. Post through existing printf | curl payload@-.

Tests

Acceptance in posix-hook-payload-delivery.test.ts against a real loopback listener:

Abandoned stdin (empty + written-never-closed) Closed-stdin Stop payload
Pre-fix FAIL timedOut: true pass
Post-fix PASS (~1.3s, event posted) pass
Neutered (unbounded cat restored) FAIL timedOut: true again pass

Also: env-missing abandoned stdin exits in <500ms; lifecycle suite still covers large payloads and empty PATH.

Follow-ups (not this PR)

  • Windows env-present curl --data-urlencode payload@- is not bounded by --max-time (curl reads @- to EOF before the transfer timer). #11549 only covers env-missing.
  • wrapPosixHookCommand missing-script drain is still unbounded cat.

Verified

  • pnpm vitest run --config config/vitest.config.ts on the antigravity POSIX delivery, hook-service, Windows delivery (skipped on macOS), and managed-hook stdin lifecycle suites
  • pnpm typecheck
  • node config/scripts/check-changed-code-quality.mjs
  • node config/scripts/check-react-doctor-changed.mjs

Could not verify

  • A live Antigravity TUI session (print-mode OAuth from a temp HOME)
  • Windows env-present payload@- hang on this machine

Antigravity can omit stdin or leave the pipe open until the hook exits.
Unbound cat never reached curl, so the CLI waited on the hook and the
sidebar stayed working. Capture through a watchdog-killed cat after the
Orca env guard, without changing the shared POSIX stdin contract.
@coderabbitai

coderabbitai Bot commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The Antigravity POSIX hook now validates its environment before reading stdin. It captures stdin through a background reader with a one-second watchdog, terminates the reader, and uses {} for empty input. Generated-script validation checks the required guard, file-descriptor capture, and cleanup command. Tests cover script structure, lifecycle behavior, payload delivery, open or closed stdin, and missing Orca configuration.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description explains the problem, fix, tests, and follow-ups, but it omits several required template sections and checklist items. Add the required ELI5, What Changed, Why, Linked Issue link, Visual Proof, AI Disclosure, Review, boundary, Notes, and completed Checklist sections.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly and concisely describes the main change: bounding POSIX hook stdin to prevent Antigravity hangs.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c633c48-630f-47d1-8f25-8271e14f692c

📥 Commits

Reviewing files that changed from the base of the PR and between 2a68b78 and 7e5c5d6.

📒 Files selected for processing (5)
  • config/scripts/verify-agent-hook-stdin-lifecycle.mjs
  • src/main/agent-hooks/managed-hook-stdin-lifecycle.test.ts
  • src/main/antigravity/hook-script.ts
  • src/main/antigravity/hook-service.test.ts
  • src/main/antigravity/posix-hook-payload-delivery.test.ts

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

Comment thread src/main/antigravity/hook-script.ts
Comment on lines +215 to +230
it('exits when a payload line is written but stdin is never closed', async () => {
const { scriptPath, port, posts } = await installAndListen()
const result = await runScript(
scriptPath,
{ ...orcaEnv(port), ORCA_ANTIGRAVITY_EVENT: 'Stop' },
ABANDONED_LINE_PAYLOAD,
false
)

expect(result.timedOut).toBe(false)
expect(result.exitCode).toBe(0)
expect(result.elapsedMs).toBeLessThan(EXIT_BUDGET_MS)
expect(result.stdout.trim()).toBe('{"decision":""}')
expect(posts.some((post) => post.hookEventName === 'Stop' && post.token === HOOK_TOKEN)).toBe(
true
)

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert the payload for the open-stdin case.

This test verifies only the event name and token. If the watchdog terminates the reader before it consumes ABANDONED_LINE_PAYLOAD, the hook can post {} and this test still passes.

Assert one post and compare its payload to ABANDONED_LINE_PAYLOAD.trimEnd().

🧰 Tools
🪛 GitHub Check: tests node 24 4/16

[failure] 224-224: src/main/antigravity/posix-hook-payload-delivery.test.ts > Antigravity POSIX hook payload delivery > exits when a payload line is written but stdin is never closed
AssertionError: expected true to be false // Object.is equality

  • Expected
  • Received
  • false
  • true

❯ src/main/antigravity/posix-hook-payload-delivery.test.ts:224:29

🪛 GitHub Check: tests node 26 4/16

[failure] 224-224: src/main/antigravity/posix-hook-payload-delivery.test.ts > Antigravity POSIX hook payload delivery > exits when a payload line is written but stdin is never closed
AssertionError: expected true to be false // Object.is equality

  • Expected
  • Received
  • false
  • true

❯ src/main/antigravity/posix-hook-payload-delivery.test.ts:224:29

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Codex revision complete.

What changed:

  • The POSIX Antigravity hook now supervises the actual stdin reader process directly instead of a brace-group shell.
  • It silently probes command -p cat with no input, keeps the existing command cat fallback, and suppresses expected killed-reader wait diagnostics.
  • Generated-script checks now assert that exact structure.
  • The acceptance case now runs with an empty PATH, preserves trailing spaces, removes only the final newline, asserts the exact posted payload, and requires empty stderr.

What stayed and why:

  • The early missing-environment exit remains because Antigravity may provide no stdin at all; waiting for EOF there can recreate the loading hang.
  • The fd 3 handoff, one-second watchdog, gate JSON, empty-object fallback, and existing curl payload delivery remain because they are the focused lifecycle contract for env-present hooks.

Non-vacuity proof:

  • Restoring the unbounded EOF capture made the acceptance case fail after about 4.3 seconds with timedOut: true.
  • Removing the fd 3 handoff stayed bounded but failed exact delivery: the received payload was {} instead of {"fullyIdle":false} followed by two trailing spaces.

Verification:

  • 4 affected test files: 24 passed, 5 platform skips.
  • pnpm typecheck passed.
  • Changed-code quality passed with zero new findings.
  • React Doctor passed with zero issues.
  • The isolated Electron build generated all 12 managed POSIX hooks; the lifecycle verifier passed exact 1,200,070-byte forwarding for all 12, both launcher cases, and the Claude/Devin skip case.
  • git diff --check passed.

@nwparker

Copy link
Copy Markdown
Contributor

I ran the generated POSIX scripts from the current investigation branch and this PR (d9179c8b7fa1c30d3d051b1e1e88ceff63ea486b) against a real loopback HTTP listener on macOS. This confirms the abandoned-stdin hang and the watchdog's benefit, but also identifies two cases to address before adoption:

Input Current script Proposed script
Empty stdin pipe left open Still blocked at 3 seconds; killed by the probe Exits in 1.07 seconds; posts {}
Valid JSON in two writes separated by 1.5 seconds, then EOF Posts the complete valid JSON; exits normally Writer gets EPIPE; hook posts only {"hook_event_name":"Stop" (invalid JSON)
Complete Stop event, readable endpoint file, missing listener port/token Writes the durable spool record Exits without a spool record

The slow-write case is a 54-byte payload, not a volume stress test: it checks scheduling/transport delay within the hook's existing 10-second host budget. Killing cat after one second treats incomplete input as a successful capture and silently forwards it.

The spool difference reflects newer code on the target branch: buildPosixHookSpoolLines now preserves lifecycle events when the listener is unavailable. A rebase needs to retain that behavior rather than returning before capture/spooling.

For this comparison I preserved the proposed shell script and supplied the same POSIX_HOOK_STDIN_READER_COMMAND = 'command -p cat' constant from this PR's branch when bundling it against the newer tree. No real user hooks or auth files were modified. These are direct generated-script tests, not proof that installed agy currently abandons stdin; a separate installed-agy run loaded its hooks bundle but generation stopped at authentication 401.

The shared POSIX_HOOK_JSON_STDIN reader is worth checking for reuse: it can return on complete JSON without EOF and already covers chunked Unicode input. Its no-Python fallback still uses unbounded cat, so selecting it alone would not establish a complete cross-host fix.

@nwparker

Copy link
Copy Markdown
Contributor

The revised implementation is now in draft #21634, stacked on #21628. It reuses JSON-completion reading, bounds the no-interpreter fallback, rejects timed-out capture instead of forwarding a prefix, and preserves current offline spooling.

Eight real-process tests pass, including delayed Unicode input, abandoned/open stdin, missing interpreters, offline storage, and descendant cleanup. Before/after loopback evidence is attached to the draft. The installed agy still returns generation HTTP 401, so this verifies the generated hook processes, not a successful live agent turn. Linux/SSH and actual Windows validation remain pending.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Complete-JSON reading landed in #24596. Any remaining hang in its fallback should be fixed in the current shared reader, so the older watchdog implementation can be retired while that work remains tracked.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants