Skip to content

fix(proxy): harden updater fallback and terminal errors - #1166

Merged
murdore merged 1 commit into
releasefrom
fix/proxy-updater-fallback-observability
Jul 15, 2026
Merged

murdore merged 1 commit into
releasefrom
fix/proxy-updater-fallback-observability

Conversation

@murdore

@murdore murdore commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

Hardens the Claude proxy updater, fallback execution, stream termination, and adapter-level error telemetry based on the recent live failure analysis.

Root Causes Addressed

  • launchd-managed proxies did not spawn the guard that contained the updater, so auto-update could never run
  • package-manager detection could select a working pnpm from an unrelated global root instead of the npm installation actually running NeuroLink
  • restart safety inferred quiet traffic from debug-log timestamps and could interrupt active streams
  • configured and auto-provider fallback failures were discarded, fallback streams could hang indefinitely, and transient fallback transport failures had no bounded retry
  • Anthropic SSE error/early-close events could be converted into an OpenAI-compatible success terminator
  • uncaught adapter errors such as fetch failed bypassed structured request/error statistics

Changes

  • add a dedicated launchd updater worker; automatic updates are enabled by default and can be disabled with NEUROLINK_PROXY_AUTO_UPDATE=off|false|0
  • resolve only a writable npm/pnpm global root that owns the running entry script; log all candidates plus captured install stdout/stderr
  • track active requests through response-body completion and require zero active requests plus a two-minute idle window before install and again before restart
  • replace bootout/bootstrap with atomic launchctl kickstart -k; never rewrite or unload the stable plist during an update
  • add bounded fallback initialization/stream idle timeouts, one retry for transient network failures, upstream abort on timeout, and preserved terminal fallback diagnostics
  • propagate Anthropic SSE errors and incomplete streams as OpenAI server_error events without fake [DONE] success
  • record invalid JSON and uncaught adapter failures in request logs, body capture, and usage stats while returning a generic client-safe 502
  • expose request activity and updater worker state through /status and neurolink proxy status

Testing

  • pnpm run build including package/declaration build, browser build, CLI build, and publint
  • CLI TypeScript check
  • focused proxy reliability tests: 48/48
  • production bugfix regression suite: 89/89
  • OpenAI proxy format suite: 10/10
  • remaining unit stages after model-pool: 58/58
  • isolated packaged proxy smoke on port 55789: health/status, invalid JSON 400, structured 1 request/1 error/0 rate limits, active request count returned to zero
  • real machine read-only installer probe selected NVM npm 11.12.1 owning the installed package and rejected the unrelated pnpm root

pnpm run test:unit has one pre-existing failure: test/modelPool.test.ts times out at its fixed 5-second limit. It reproduces unchanged in the untouched original checkout; all 18 other tests in that file pass.

Deployment Notes

The currently installed older proxy cannot gain the new updater worker by itself. Upgrade once to the release containing this PR and restart through the normal operator workflow; subsequent launchd-managed releases auto-update by default. Set NEUROLINK_PROXY_AUTO_UPDATE=off before that restart to opt out. Updater diagnostics are written to ~/.neurolink/logs/proxy-updater.log.

The live proxy was not restarted, upgraded, reconfigured, or used for test traffic while developing this change. No dependencies were added.

Summary by CodeRabbit

  • New Features
    • Proxy auto-updates are enabled by default, with clearer behavior for launchd-managed vs foreground runs.
    • Added runtime activity introspection (active requests, idle detection) and “Auto-update” status visibility, plus updater diagnostics output.
  • Bug Fixes
    • Hardened streaming and cancellation handling with more accurate SSE/OpenAI-compatible error frames.
    • Improved fallback translation reliability with retries, timeouts, and clearer “fallback also failed” messaging.
  • Documentation
    • Updated proxy guard, environment variables (including package-manager selection), and cleanup/updater guidance.
  • Tests
    • Added coverage for activity tracking, installer selection, updater fallback, and stream/interruption behavior.

Copilot AI review requested due to automatic review settings July 15, 2026 15:41
@vercel

vercel Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
neurolink Ready Ready Preview, Comment Jul 15, 2026 6:08pm

@github-actions

github-actions Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

✅ Single Commit Policy - COMPLIANT

Status: Policy requirements met • 1 commit • Valid format • Ready for merge

📊 View validation details

📝 Commit Details

  • Hash: d2f93bd6b3bb86aa291d73adfe3ca353c6544497
  • Message: fix(proxy): harden updater fallback and error telemetry
  • Author: Sachin Sharma

✅ Validation Results

  • Single commit requirement met
  • No merge commits in branch
  • Semantic commit message format verified
  • Ready for squash merge to release branch

🤖 Automated validation by NeuroLink Single Commit Enforcement

@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8ba4eeae-9686-4ff4-8e76-31377527c447

📥 Commits

Reviewing files that changed from the base of the PR and between 3dda352 and d2f93bd.

📒 Files selected for processing (13)
  • docs/features/claude-proxy-config-reference.md
  • docs/features/claude-proxy.md
  • src/cli/commands/proxy.ts
  • src/lib/proxy/globalInstaller.ts
  • src/lib/proxy/openaiFormat.ts
  • src/lib/proxy/proxyActivity.ts
  • src/lib/server/routes/claudeProxyRoutes.ts
  • src/lib/server/routes/openaiProxyRoutes.ts
  • src/lib/types/cli.ts
  • src/lib/types/proxy.ts
  • test/continuous-test-suite-bugfixes.ts
  • test/proxyReliabilityHardening.test.ts
  • test/proxyUpdaterFallback.test.ts

📝 Walkthrough

Walkthrough

The proxy separates launchd updater workers from fail-open cleanup, tracks runtime activity and request errors, resolves global installers for self-updates, hardens Claude fallback retries and timeouts, and reports streaming lifecycle outcomes.

Changes

Proxy runtime and telemetry

Layer / File(s) Summary
Runtime activity and request telemetry
src/lib/types/proxy.ts, src/lib/proxy/proxyActivity.ts, src/cli/commands/proxy.ts, test/proxyUpdaterFallback.test.ts
Proxy requests remain active until response bodies settle, retain request metadata, record terminal errors, and expose activity through status reporting.

Launchd updater and installer

Layer / File(s) Summary
Launchd updater and global installer
src/lib/types/cli.ts, src/lib/types/proxy.ts, src/lib/proxy/globalInstaller.ts, src/cli/commands/proxy.ts, docs/features/claude-proxy*.md, test/*
Launchd-managed proxies start updater-only workers, wait for idle windows, resolve writable npm/pnpm installers, validate trampolines, and restart through launchd kickstart. Documentation and regression checks reflect the new behavior.

Fallback and streaming reliability

Layer / File(s) Summary
Fallback and streaming failure handling
src/lib/server/routes/claudeProxyRoutes.ts, src/lib/proxy/openaiFormat.ts, src/lib/server/routes/openaiProxyRoutes.ts, test/proxyUpdaterFallback.test.ts
Fallback translation adds timeout-bounded retries and failure context; streaming paths emit terminal errors and distinguish successful completion, upstream failure, and client cancellation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Launchd
  participant UpdaterWorker
  participant ProxyStatus
  participant GlobalInstaller
  participant PackageManager
  Launchd->>UpdaterWorker: Start updater-only worker
  UpdaterWorker->>ProxyStatus: Poll runtime activity
  ProxyStatus-->>UpdaterWorker: Return quiet-window state
  UpdaterWorker->>GlobalInstaller: Resolve owning installer
  GlobalInstaller->>PackageManager: Probe and install package
  UpdaterWorker->>Launchd: Kickstart validated proxy
Loading

Possibly related PRs

  • juspay/neurolink#1029: Introduced the OpenAI-compatible endpoint and related streaming bridge used by these lifecycle and error-handling changes.
  • juspay/neurolink#1163: Touches the same proxy launchd, fail-open guard, and updater control flow.

Suggested labels: released

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main proxy hardening work around updater behavior, fallback handling, and terminal error handling.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/proxy-updater-fallback-observability

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed due to a network error.


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.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

github-actions Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Documentation Validation Results

🚀 Documentation validation passed!

Check Status Result
Frontmatter Validation ✅ Passed
TypeScript Check ✅ Passed
Build ✅ Passed
Link Validation ✅ Passed

📦 Build artifact uploaded successfully. Ready for deployment preview.

Commit: 4e3e41b4d28c85bcf5542fc1d6b032d4128c2cf4 | Workflow: View logs

Copilot AI 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.

Pull request overview

Hardens the NeuroLink Claude proxy’s self-updater and fallback/stream error handling to prevent unsafe restarts, reduce indefinite hangs, and improve operator diagnostics + telemetry accuracy.

Changes:

  • Introduces in-process request activity tracking and exposes it via /status / neurolink proxy status to gate updater install/restart on true runtime idleness.
  • Replaces updater package-manager selection with a “global root + bin dir must be writable and must own the running install” resolver, and adds richer install-failure diagnostics.
  • Adds bounded fallback initialization/stream idle timeouts + a retry for transient network failures, and propagates Anthropic SSE terminal errors/incomplete streams as OpenAI server_error events.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/proxyUpdaterFallback.test.ts Adds focused tests for proxy activity tracking, runtime error finalization, installer resolution, fallback retry/timeout behavior, and stream error bridging.
test/proxyReliabilityHardening.test.ts Updates launchd/updater source invariants to reflect the new updater worker + installer probing behavior.
test/continuous-test-suite-bugfixes.ts Updates continuous regression checks to match the new kickstart/installer/updater logging behavior.
src/lib/types/proxy.ts Adds types for runtime activity snapshots, request metadata, and global-installer resolution/probes.
src/lib/types/cli.ts Adds updaterOnly guard arg and persists updater PID in proxy state.
src/lib/server/routes/openaiProxyRoutes.ts Tracks terminal stream errors and finalizes lifecycle after streaming completes (or on cancel/error).
src/lib/server/routes/claudeProxyRoutes.ts Adds fallback timeouts, retry-on-transient-network behavior, and preserves fallback failure diagnostics for terminal errors.
src/lib/proxy/proxyActivity.ts Implements request activity tracking and response-body completion tracking.
src/lib/proxy/openaiFormat.ts Extends Claude→OpenAI stream transform to treat Anthropic error/incomplete streams as terminal error events (no fake success).
src/lib/proxy/globalInstaller.ts Adds global installer resolution/probing and install failure description utilities.
src/cli/commands/proxy.ts Spawns a dedicated updater-only worker for launchd installs; gates updates on runtime idleness; improves error telemetry and /status payload.
docs/features/claude-proxy.md Documents updater-only worker behavior and new default auto-update semantics.
docs/features/claude-proxy-config-reference.md Updates guard/updater documentation and adds the new environment variables for installer selection.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/proxy/globalInstaller.ts
Comment thread src/lib/proxy/openaiFormat.ts
Comment thread src/lib/server/routes/openaiProxyRoutes.ts

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/lib/server/routes/openaiProxyRoutes.ts (1)

235-294: 📐 Maintainability & Code Quality | 🔵 Trivial

Consider reusing the shared stream-outcome tracker instead of a bespoke implementation.

This hand-rolled trackedStream/finishLifecycle/lifecycleWritten pattern duplicates the same "track terminal stream outcome (success/error/cancel) exactly once" problem already solved by createStreamTerminalOutcomeTracker/trackUpstreamReadableStream in ../../proxy/streamOutcome.js, which claudeProxyRoutes.ts already imports and uses. Extracting/exporting that helper for reuse here would avoid two parallel implementations of the same lifecycle-tracking logic drifting apart over time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/server/routes/openaiProxyRoutes.ts` around lines 235 - 294, Replace
the bespoke trackedStream, finishLifecycle, and lifecycleWritten logic with the
shared createStreamTerminalOutcomeTracker/trackUpstreamReadableStream utilities
from ../../proxy/streamOutcome.js. Export or adapt the existing helper as
needed, preserving the current 200 success, 502 stream-error, and 499
client-cancelled lifecycle outcomes and ensuring each outcome is recorded
exactly once.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cli/commands/proxy.ts`:
- Around line 1273-1275: Sanitize the request-derived model value before
interpolating it into the unstructured logger.always message in the proxy
request logging flow. Update the logging around model and preserve the existing
method, path, stream, and toolCount output while ensuring CR/LF characters
cannot create forged log entries.
- Around line 714-721: Move the updater log directory creation and file opening
in the proxy startup flow into the existing try/catch surrounding updater setup.
If mkdirSync or openSync fails, catch the error and disable or skip updater
logging without aborting launchd-managed proxy startup; preserve normal logging
when setup succeeds.

---

Nitpick comments:
In `@src/lib/server/routes/openaiProxyRoutes.ts`:
- Around line 235-294: Replace the bespoke trackedStream, finishLifecycle, and
lifecycleWritten logic with the shared
createStreamTerminalOutcomeTracker/trackUpstreamReadableStream utilities from
../../proxy/streamOutcome.js. Export or adapt the existing helper as needed,
preserving the current 200 success, 502 stream-error, and 499 client-cancelled
lifecycle outcomes and ensuring each outcome is recorded exactly once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7936c08f-e384-41bd-8d28-21fc1a9fc31b

📥 Commits

Reviewing files that changed from the base of the PR and between ebafce0 and 3dda352.

📒 Files selected for processing (13)
  • docs/features/claude-proxy-config-reference.md
  • docs/features/claude-proxy.md
  • src/cli/commands/proxy.ts
  • src/lib/proxy/globalInstaller.ts
  • src/lib/proxy/openaiFormat.ts
  • src/lib/proxy/proxyActivity.ts
  • src/lib/server/routes/claudeProxyRoutes.ts
  • src/lib/server/routes/openaiProxyRoutes.ts
  • src/lib/types/cli.ts
  • src/lib/types/proxy.ts
  • test/continuous-test-suite-bugfixes.ts
  • test/proxyReliabilityHardening.test.ts
  • test/proxyUpdaterFallback.test.ts

Comment thread src/cli/commands/proxy.ts
Comment thread src/cli/commands/proxy.ts

@Tara-ag Tara-ag 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.

Review summary

Reviewed 13 changed files. I did not raise any new blocking issues this run — the existing review threads (Copilot + CodeRabbit) already cover the substantive concerns I would have flagged:

  • writableDirectory() should also require traverse (X_OK) permission (src/lib/proxy/globalInstaller.ts:33) — already raised.
  • Anthropic SSE error events should terminate the transform rather than leave the stream open (src/lib/proxy/openaiFormat.ts:932) — already raised.
  • OpenAI bridge stream cancel path should not misclassify a terminal stream error as 499 client_cancelled (src/lib/server/routes/openaiProxyRoutes.ts:290) — already raised.
  • Updater log setup should fail open so an unwritable log dir does not abort launchd-managed startup (src/cli/commands/proxy.ts:714-721) — already raised.
  • Request-derived model should be sanitized before unstructured logging (src/cli/commands/proxy.ts:1273-1275) — already raised.

Blocking-criteria check

  • No hardcoded secrets or credentials.
  • No critical security vulnerabilities (injection, SSRF, unsafe eval, secret/PII exposure) introduced.
  • No breaking change to the public SDK API; changes are confined to the CLI/proxy path.
  • No violation of a non-negotiable CLAUDE.md Critical Rule that ESLint does not enforce.
  • Fewer than 3 new MAJOR issues raised by this review.

Notes

  • The new request-activity tracking (proxyActivity.ts) correctly holds requests open until the response body settles, and the updater gates install/restart on true runtime idleness.
  • The global-installer resolver correctly rejects package managers whose writable global root does not own the running entry script, addressing the reported pnpm/npm shadowing problem.
  • Fallback translation now has bounded initialization/stream idle timeouts, upstream abort on timeout, and one transient-network retry.
  • Anthropic SSE errors and incomplete streams are propagated as OpenAI server_error events without a fake [DONE] success terminator.
  • Uncaught adapter errors and invalid JSON are recorded in request logs, body capture, and usage stats while returning a generic client-safe response.

Approving — please address the existing reviewer comments before merging.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore
murdore force-pushed the fix/proxy-updater-fallback-observability branch from 0fc02b1 to d2f93bd Compare July 15, 2026 18:07
@murdore
murdore merged commit 4ed5e39 into release Jul 15, 2026
15 of 16 checks passed
@murdore
murdore deleted the fix/proxy-updater-fallback-observability branch July 15, 2026 18:08
@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 9.87.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

@Tara-ag Tara-ag 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.

Review Summary

Reviewed 13 changed files. Raised 6 new inline comments (2 Major, 4 Minor). No blocking criteria are met this run.

New issues

Major (2)

  • src/lib/proxy/openaiFormat.ts:932 — Terminal Anthropic error events emit an OpenAI error frame but do not terminate the transform, so the stream can linger if the upstream connection stays alive.
  • src/lib/server/routes/openaiProxyRoutes.ts:290 — The stream cancel path always records 499 client_cancelled, even when a terminal upstream stream error was already observed, misclassifying the lifecycle outcome.

Minor (4)

  • src/lib/proxy/globalInstaller.ts:33 — writableDirectory() checks only W_OK; directories also need traverse (X_OK) permission for reliable installability detection.
  • src/cli/commands/proxy.ts:705 — Updater log file is created with default umask; consider 0o700 directory and 0o600 log file permissions.
  • src/cli/commands/proxy.ts:1275 — Request-derived model is logged unsanitized; CR/LF characters can forge log entries.
  • src/lib/proxy/proxyActivity.ts:42 — trackProxyResponse drops the cancellation reason when cancelling the upstream reader.

Existing comments

Acknowledged the 5 prior review comments from copilot-pull-request-reviewer and coderabbitai; I did not duplicate them.

Decision

Approve. The issues raised are correctness/quality improvements, not security blockers or public-API-breaking changes. The two Major items should be addressed in a follow-up to avoid stream-lifecycle misclassification.

Comment thread src/lib/proxy/openaiFormat.ts
Comment thread src/lib/server/routes/openaiProxyRoutes.ts
Comment thread src/lib/proxy/globalInstaller.ts
Comment thread src/cli/commands/proxy.ts
Comment thread src/cli/commands/proxy.ts
Comment thread src/lib/proxy/proxyActivity.ts

This branch was successfully deployed

1 active deployment
Preview — d2f93bd6 Deployed Jul 15, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants