Skip to content

fix(gateway): unbreak daemon after onboard — remove duplicate block + guard push attrs#1484

Merged
MervinPraison merged 2 commits into
mainfrom
fix/gateway-daemon-onboard
Apr 21, 2026
Merged

fix(gateway): unbreak daemon after onboard — remove duplicate block + guard push attrs#1484
MervinPraison merged 2 commits into
mainfrom
fix/gateway-daemon-onboard

Conversation

@MervinPraison

@MervinPraison MervinPraison commented Apr 20, 2026

Copy link
Copy Markdown
Owner

Problem

After a fresh curl -fsSL https://praison.ai/install.sh | bash on v4.6.22, the onboarding wizard completes successfully and installs the launchd daemon, but:

❯ praisonai gateway status
✅ Daemon service: Running (launchd)
Gateway not reachable at http://127.0.0.1:8765/health
Error: <urlopen error [Errno 61] Connection refused>

Root causes (two)

1. IndentationError crash loop in praisonai/gateway/server.py

start_with_config() had its last 28 lines duplicated four times with broken indentation (84 spurious lines, 1809-1892). Python refused to import the module; launchd's KeepAlive=true crash-looped the daemon, producing a 68 MB bot-stderr.log in minutes. Port 8765 never opened, hence 'Connection refused'.

2. AttributeError: _push_enabled → HTTP 500

Once the daemon stayed up, /health still failed:

File ".../praisonai/gateway/server.py", line 964, in health
    if self._push_enabled:
AttributeError: 'WebSocketGateway' object has no attribute '_push_enabled'

PR #1483 moved PushClient and transports out of the core gateway into the wrapper, but left stale references (_push_enabled, _channel_mgr, _presence_mgr, _redis_pubsub) in health().

Fix (minimal, 1 file)

  • Delete the 84 duplicated lines so start_with_config() ends cleanly at await self.stop_channels().
  • Guard each push attribute access in health() with getattr(self, "_attr", default) so core gateways without push report healthy.

Verification

launchctl kickstart -k gui/$(id -u)/ai.praison.bot
curl -sS http://127.0.0.1:8765/health
# {"status": "healthy", "uptime": 3.6, "agents": 1, ...}
praisonai gateway status
# ✅ Daemon service: Running (launchd)
# Gateway Status: healthy

Diff: 1 file changed, 11 insertions(+), 92 deletions(-)

Summary by CodeRabbit

  • Refactor
    • Improved server stability by adding defensive checks to avoid missing push-related state
    • Simplified startup/shutdown flow to remove redundant shutdown paths and ensure cleaner task cancellation

…ock + guard push attrs

Two root causes for 'praisonai gateway status' showing the launchd
daemon running while port 8765 refuses connections after
curl | bash onboarding in v4.6.22:

1. server.py had 84 lines of duplicated/malformed code appended to
   start_with_config(), causing IndentationError at module import time.
   The launchd service crash-looped (KeepAlive=true) and filled
   bot-stderr.log with ~68MB of tracebacks. Removed the duplicated
   tail so the method ends correctly at stop_channels().

2. /health references self._push_enabled / _channel_mgr / _presence_mgr /
   _redis_pubsub, but PR #1483 moved push infra out of the core gateway.
   Once the daemon stayed up, the first health probe raised
   AttributeError: 'WebSocketGateway' object has no attribute '_push_enabled',
   producing HTTP 500. Guard every push attribute access with getattr
   so health reports cleanly when push is disabled.

Verified: fresh launchd kickstart, curl http://127.0.0.1:8765/health
returns {"status": "healthy", ...}; praisonai gateway status reports
'Gateway Status: healthy'.
Copilot AI review requested due to automatic review settings April 20, 2026 23:38

@greptile-apps greptile-apps 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.

MervinPraison has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4adb6b5-1f8d-4d8f-8c1d-055568ab5574

📥 Commits

Reviewing files that changed from the base of the PR and between 0ca853a and 5ce99da.

📒 Files selected for processing (1)
  • src/praisonai/praisonai/gateway/server.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/praisonai/praisonai/gateway/server.py

📝 Walkthrough

Walkthrough

The gateway server module was refactored to avoid attribute errors and eliminate duplicated shutdown scaffolding. health() now uses defensive getattr lookups for push-related state; start_with_config() consolidates signal-handler registration and shutdown into a single try/finally that cancels tasks and calls stop_channels().

Changes

Cohort / File(s) Summary
Gateway Server Stability
src/praisonai/praisonai/gateway/server.py
Replaced direct private-attribute access with getattr-based checks in health() to conditionally populate push-related status (_push_enabled, _channel_mgr, _presence_mgr, _redis_pubsub, _client). Removed duplicated signal-handler registration and multiple _run_all()/finally shutdown blocks in start_with_config(), leaving a single consolidated shutdown path that cancels config watch and scheduler tasks and calls stop_channels().

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Duplication pruned, attributes checked with care,
The gateway hops steady through cleaner air,
One graceful shutdown, no scattered alarms,
Push states peeked safely, no accidental harms,
I nibble on bugs and leave tidy charms. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly references the two main changes: removing a duplicate block and guarding push attributes. It accurately summarizes the core fixes applied to unbreak the gateway daemon after onboarding.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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/gateway-daemon-onboard

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 and usage tips.

@MervinPraison

Copy link
Copy Markdown
Owner Author

@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first — incorporate their findings.

Review areas:

  1. Bloat check: Are changes minimal and focused? Any unnecessary code or scope creep?
  2. Security: Any hardcoded secrets, unsafe eval/exec, missing input validation?
  3. Performance: Any module-level heavy imports? Hot-path regressions?
  4. Tests: Are tests included? Do they cover the changes adequately?
  5. Backward compat: Any public API changes without deprecation?
  6. Code quality: DRY violations, naming conventions, error handling?
  7. Address reviewer feedback: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review
  8. Suggest specific improvements with code examples where possible

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request improves the robustness of the health endpoint in server.py by implementing defensive attribute access using getattr and removes a significant amount of redundant code. A review comment suggests extending this defensive approach to the internal _client attribute of the Redis pub-sub manager to maintain consistency and prevent potential attribute errors.

push_status["online_clients"] = presence_mgr.get_online_count()
redis_pubsub = getattr(self, "_redis_pubsub", None)
if redis_pubsub is not None:
push_status["redis_connected"] = redis_pubsub._client is not None

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.

medium

To be truly defensive as noted in the comment on line 963, it is safer to use getattr when accessing the private _client attribute of redis_pubsub. Since these attributes are dynamically injected by a wrapper and are considered "stale" in the core gateway, their internal structure might not be guaranteed. Accessing a private attribute directly could lead to an AttributeError if the implementation of the injected object changes or if it differs from the expected RedisPubSubAdapter type.

Suggested change
push_status["redis_connected"] = redis_pubsub._client is not None
push_status["redis_connected"] = getattr(redis_pubsub, "_client", None) is not None

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

🧹 Nitpick comments (1)
src/praisonai/praisonai/gateway/server.py (1)

963-977: Defensive getattr guards look correct and fix the reported AttributeError.

The block correctly short-circuits when _push_enabled is absent or falsy, and each sub-manager is fetched with a safe default before use. This matches the stated fix for the daemon returning HTTP 500 on /health after push wrappers were extracted in #1483, and preserves the prior shape of result["push"] when push infra is actually wired in.

One minor note (pre-existing, not introduced here): line 974 still reaches into redis_pubsub._client, a private attribute of the pubsub object. If a future refactor of that class renames _client, /health will regress again the same way it did here. Consider exposing a small public accessor (e.g. is_connected()) on the pubsub wrapper in a follow-up so the gateway doesn't depend on its internals.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/gateway/server.py` around lines 963 - 977, The
health-check currently inspects redis_pubsub._client (alongside _push_enabled,
_channel_mgr.list_channels() and _presence_mgr.get_online_count()) which works
now but relies on a private attribute; update the code to call a public accessor
on the pubsub wrapper instead: add/use a method like is_connected() on the
pubsub class and replace the check redis_pubsub._client is not None with
redis_pubsub.is_connected() (or, if is_connected() doesn’t exist yet, add it to
the pubsub wrapper to return connection status), keeping the rest of the logic
that builds result["push"] unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/praisonai/praisonai/gateway/server.py`:
- Around line 963-977: The health-check currently inspects redis_pubsub._client
(alongside _push_enabled, _channel_mgr.list_channels() and
_presence_mgr.get_online_count()) which works now but relies on a private
attribute; update the code to call a public accessor on the pubsub wrapper
instead: add/use a method like is_connected() on the pubsub class and replace
the check redis_pubsub._client is not None with redis_pubsub.is_connected() (or,
if is_connected() doesn’t exist yet, add it to the pubsub wrapper to return
connection status), keeping the rest of the logic that builds result["push"]
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5e9c8a5-8bc7-46d6-ab81-22847c1a3098

📥 Commits

Reviewing files that changed from the base of the PR and between 3daa6a9 and 0ca853a.

📒 Files selected for processing (1)
  • src/praisonai/praisonai/gateway/server.py

@MervinPraison

Copy link
Copy Markdown
Owner Author

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions

Phase 2: FIX Valid Issues
4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
5. Push all code fixes directly to THIS branch (do NOT create a new PR)
6. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
7. If all issues are resolved, approve the PR / close the Issue
8. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


Final Architecture Review

Phase 1: Architecture Review per AGENTS.md

  • Read AGENTS.md guidelines
  • Review protocol-driven design compliance ✅
  • Check backward compatibility ✅
  • Verify performance impact ✅

Phase 2: Fix Valid Issues from Reviewers

  • Analyze Gemini/CodeRabbit/Qodo/Copilot feedback
  • Fix private attribute access issue (redis_pubsub._client)
  • Push fixes to branch

Phase 3: Final Verdict

  • Approve PR - all issues resolved
  • Provide summary of modifications

Architecture Review Results

Protocol-Driven Core Compliance: The gateway module uses defensive attribute checking with getattr(), which is appropriate for the wrapper layer accessing core gateway components.

Backward Compatibility: The fix maintains backward compatibility by preserving the same health response structure when push infrastructure is present.

Performance Impact: No performance regressions - the defensive getattr() calls only happen on /health endpoint calls, not hot paths.

Issue Analysis & Resolution

The PR correctly fixes both identified issues:

  1. IndentationError: Removed 84 duplicated lines at end of start_with_config()
  2. AttributeError: Added defensive getattr() guards for push attributes

Additional Fix Applied:

  • ✅ Fixed redis_pubsub._client private attribute access (line 974) with defensive getattr() as recommended by CodeRabbit and Gemini reviewers

Files Modified

  • src/praisonai/praisonai/gateway/server.py: Applied defensive attribute access for redis_pubsub._client

Final Verdict: ✅ APPROVED

The PR successfully resolves the daemon crash loop and HTTP 500 health endpoint issues. The additional defensive guard ensures robustness against future Redis adapter implementation changes. All architectural guidelines from AGENTS.md are followed.

Ready for merge - minimal, focused fix with proper defensive programming patterns.

Apply defensive getattr() to redis_pubsub._client to maintain consistency
with other push attribute guards and prevent future AttributeError if
the Redis adapter implementation changes.

Addresses feedback from CodeRabbit and Gemini reviewers.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>

@greptile-apps greptile-apps 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.

MervinPraison has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@MervinPraison MervinPraison merged commit 5b10dd6 into main Apr 21, 2026
12 checks passed
@MervinPraison MervinPraison deleted the fix/gateway-daemon-onboard branch June 12, 2026 23:15
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