Skip to content

Add TCP support for Python Executor and local debugging guide#1906

Merged
renuka-fernando merged 9 commits intomainfrom
python-policies
May 8, 2026
Merged

Add TCP support for Python Executor and local debugging guide#1906
renuka-fernando merged 9 commits intomainfrom
python-policies

Conversation

@renuka-fernando
Copy link
Copy Markdown
Contributor

Purpose

Fix #1707
Fix #1704

Previously, communication between the Go Policy Engine and the Python Executor was hardcoded to use Unix Domain Sockets (UDS). This architectural constraint prevented the Python Executor from running in a separate container (e.g., for future GPU-isolated workloads) and made local development and debugging of Python policies extremely difficult, as developers could not easily attach IDE debuggers (like VS Code) or use tools like pdb without complex container modifications.

Goals

  • Introduce dual-mode (UDS and TCP) gRPC transport support for both the Go Policy Engine (client) and the Python Executor (server).
  • Enable 12-factor app compliance for the Python Executor by removing unnecessary config file reading and relying strictly on environment variables and CLI flags.
  • Establish a clear, documented workflow for developers to run the Python Executor locally on their host machine for rapid iteration and debugging.

Approach

  1. Python Executor TCP Support: Updated main.py and server.py to accept a flexible --listen argument that automatically detects and binds to either a UDS path or a TCP address (e.g., localhost:9010).
  2. **Configuration:**Can configure exclusively via CLI flags (--listen, --workers, --timeout, etc.) and environment variables.
  3. Go Client TCP Support: Refactored client.go to dial dynamically based on the configuration provided in config.toml ([python_executor.server]).
  4. Registry Versioning Sync: Ensured generator.go and the Python registry continue to use major-version keys (e.g., prompt-compressor:v0) to maintain consistency with the Go Policy Engine's internal registry conventions.
  5. Documentation: Added a comprehensive PYTHON_DEBUG_GUIDE.md detailing the "Option 2" style local process debugging setup.

Documentation

Created gateway/PYTHON_DEBUG_GUIDE.md to document the new local debugging workflow.

Security checks

Test environment

  • macOS (Host)
  • Docker / Rancher Desktop (Lima)
  • Go 1.22+
  • Python 3.10+

Related PR: #1875

sehan-dissanayake and others added 9 commits April 30, 2026 15:05
Introduce a configurable Python executor (UDS or TCP) and wire it into the policy engine.

Key changes:
- Add [python_executor] section to config-template.toml (server mode/host/port and timeout).
- Add PythonExecutorConfig types, defaults and validation in policy engine config.
- Initialize pythonbridge from configuration in policy-engine main and use pythonbridge.IsAvailable when starting admin server.
- Refactor pythonbridge StreamManager to use address + isTCP, add Init(cfg) to configure the global manager, support TCP vs UDS dialing, expose IsAvailable, and centralize timeout handling. Update tests accordingly.
- Update Python executor server to accept a listen address (supports TCP or UDS), cleanup stale UDS files, and improve logging.
- Update Python executor main to read TOML config (tomli/tomllib), add --listen flag and config-aware defaults; add tomli to requirements for Python <3.11.
- Prevent overriding the executor listen address in docker-entrypoint; adjust docker-compose (comment out admin/metrics port mappings and set GATEWAY_CONTROLLER_HOST=host.docker.internal).
- Skip common Python virtualenv/cache/.git directories when copying policy sources in the builder.

These changes allow running the Python executor over TCP (useful for containerized setups) or UDS, provide configurable timeouts, improve startup robustness, and keep tests and tooling aligned.
Add a comprehensive PYTHON_DEBUG_GUIDE.md for running and debugging the Python Executor locally (TCP mode, VS Code/pdb tips, steps to build, run, and cleanup). Change pythonbridge factory to resolve the global StreamManager at GetPolicy time instead of carrying it on BridgeFactory: remove the StreamManager field, call GetStreamManager() in GetPolicy, and pass that instance to InitPolicy and the created bridge. Update the plugin registry template to stop injecting StreamManager into the generated BridgeFactory. This ensures the singleton StreamManager is configured after pythonbridge.Init(cfg) in main before use.
Add a new VS Code debug configuration for the Python Executor (debugpy) in .vscode/launch.json to launch the executor module with appropriate args and PYTHONPATH. Overhaul gateway/PYTHON_DEBUG_GUIDE.md: clarify the host-process debugging workflow, update the mermaid diagram, add warnings about Go module resolution and Rancher Desktop, instruct using host.docker.internal (or host IP) and specific docker-compose changes (set GATEWAY_CONTROLLER_HOST and comment Policy Engine ports), renumber and clarify steps, provide updated Docker Compose commands (run from gateway/), add cleanup guidance, and update the ports table and assorted wording tweaks.
Remove TOML config parsing and dependency; rely on environment variables for defaults instead. main.py no longer reads a --config TOML file at startup and uses PYTHON_EXECUTOR_LISTEN and PYTHON_POLICY_TIMEOUT (env) as defaults; help text updated to clarify listen can be a UDS path or host:port. requirements.txt drops tomli. VS Code launch.json entries no longer pass --config. PYTHON_DEBUG_GUIDE.md updated with Rancher Desktop (Lima) instructions to export HOST_IP and apply it to the relevant steps, plus adjusted docs to reflect that the executor binds to localhost:9010 when using TCP. These changes simplify startup and remove the toml parsing requirement.
Add support for configuring the Python executor Unix socket path and TCP mode, and wire that through runtime, config validation, tests, docs and the Docker entrypoint.

- config: add PythonExecutorServer.Path, validate host when mode=tcp and ensure timeout > 0
- pythonbridge: use a configured socket/path and TCP flag for StreamManager and availability checks (fall back to default path when unset)
- entrypoint: export PYTHON_EXECUTOR_SOCKET and start the python executor with --listen using the socket; unset PYTHON_EXECUTOR_LISTEN before launching
- tests: add TestValidate_PythonExecutorConfig covering UDS/tcp, host/port validation and timeouts
- docs: update PYTHON_DEBUG_GUIDE to clarify controller env var usage and authentication placeholder for API deploy

These changes allow switching between UDS and TCP modes and customizing the UDS path reliably across components.
Use net.JoinHostPort with strconv.Itoa for building the host:port address in the pythonbridge client (adds strconv import). Update PYTHON_DEBUG_GUIDE.md by changing fenced blocks to text and removing redundant `cd gateway` lines and minor whitespace cleanup to improve clarity.
Namespace the Python executor config under policy_engine: update configs/config-template.toml, move PythonExecutor into the PolicyEngine struct, adjust defaults, validation messages, and error text accordingly, and update main.go to use cfg.PolicyEngine.PythonExecutor. Update unit tests to reflect the new path. Documentation: delete the standalone PYTHON_DEBUG_GUIDE.md and expand gateway/DEBUG_GUIDE.md with an Option 2B for running the Python Executor locally and related instructions. Also tweak the VS Code Python Executor launch config (name change and console setting).
Add TCP support for Python Executor and local debugging guide
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Summary

This pull request adds TCP support for the Python Policy Executor, enabling it to run in separate containers and communicate with the Policy Engine over network sockets. It also introduces a comprehensive local debugging guide to facilitate development workflows.

Key Changes

Transport Configuration

  • Python Executor now supports dual-mode gRPC connectivity: Unix Domain Sockets (default) or TCP
  • Configurable via new --listen CLI argument (e.g., --listen localhost:9010 for TCP or --listen /path/to/socket for UDS)
  • Configuration can be specified via environment variable PYTHON_EXECUTOR_LISTEN or TOML config under policy_engine.python_executor.server

Policy Engine Integration

  • Go client refactored to dial the Python Executor dynamically based on configuration
  • New pythonbridge.Init() function initializes executor connection settings (address, transport mode, timeout)
  • StreamManager now supports both TCP and Unix socket connections with automatic transport selection
  • Health-check logic updated to verify executor availability based on configured mode

Configuration & Validation

  • Added PythonExecutorConfig and PythonExecutorServerConfig types to policy engine configuration
  • Centralized timeout settings for executor communication (configurable, default 30 seconds)
  • Validation ensures proper configuration of host/port for TCP mode and UDS paths for socket mode

Code Generation

  • Python executor base directory is always generated (even without policies) to prevent Docker build failures
  • Policy registry generation updated to map policies to import paths, handling both local and pip-based policies
  • Improved requirements merging across base executor and per-policy dependencies
  • Build process now skips virtual environment and cache directories during copying

Python Executor Server

  • Constructor changed from socket_path to listen_address parameter
  • Automatic mode detection: treats addresses with colons as TCP, others as UDS paths
  • Added stale socket cleanup for UDS mode to remove leftover socket files from prior runs
  • Enhanced error handling for binding failures

Docker & Startup

  • Docker entrypoint updated to enforce fixed --listen configuration (rejects --listen overrides)
  • Python executor startup explicitly invokes --listen argument with configured address
  • Removed reliance on environment-variable-resolved socket paths

Debugging & Development

  • New comprehensive Python debugging guide (gateway/PYTHON_DEBUG_GUIDE.md) with step-by-step instructions for local host-process debugging
  • VS Code launch configuration added for Python Executor with debugpy adapter
  • Guide covers setup, execution, breakpoint debugging, and cleanup procedures
  • Host module resolution notes and troubleshooting tips included

Plugin Registry

  • Template-generated registry initialization simplified: removed explicit StreamManager field assignment in favor of dynamic resolution at policy execution time

Impact

  • Enables containerized deployments where Policy Engine, Router, and Python Executor run as separate services
  • Improves local development experience with TCP-based communication compatible with host machine debuggers
  • Maintains backward compatibility with UDS-based deployment (default mode)
  • Configuration-driven approach reduces hardcoded socket paths and improves flexibility

Walkthrough

This PR enables TCP support for the Python Executor bridge alongside Unix domain sockets. It introduces configuration-driven initialization via a new PythonExecutorConfig type with connection mode, host/port, and timeout parameters, replacing hardcoded socket paths and environment-variable-based timeouts. The bridge client refactors to store a generic address plus an isTCP flag and selects TCP or UDS dialing accordingly. The BridgeFactory now resolves the StreamManager dynamically at policy retrieval time. The Python executor server and entry point now accept a configurable listen_address parameter that automatically detects TCP mode from the presence of a colon. The policy engine initializes the bridge via pythonbridge.Init() and checks availability using IsAvailable(). The builder code generator unconditionally produces a Python executor base directory and refactors artifact generation to copy local policies and merge requirements. Configuration tests and bridge tests are updated to validate the new transport modes. Documentation adds a new Option 2B debugging workflow and VS Code debug configuration.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% 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
Title check ✅ Passed The title accurately summarizes the main changes: introducing TCP support for the Python Executor and adding a debugging guide.
Description check ✅ Passed The description comprehensively addresses Purpose, Goals, Approach, Documentation, and Security checks per the template. All required sections are present and substantive.
Linked Issues check ✅ Passed All coding requirements from #1704 (TCP gRPC support) and #1707 (debug guide) are met: dual-mode transport, configuration via CLI/env vars, local debugging workflow, and comprehensive documentation provided.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the stated objectives: TCP/UDS transport support, configuration refactoring, debug tooling, and comprehensive documentation. No unrelated modifications detected.

✏️ 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 python-policies

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.

🔧 golangci-lint (2.12.1)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
gateway/gateway-runtime/docker-entrypoint.sh (1)

24-24: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Sync --py.listen guard and Python Executor startup to docker-entrypoint-debug.sh.

The comment on line 24 notes the debug entrypoint must be kept in sync. The new --py.listen guard (lines 77–80) that rejects listen overrides and the unset PYTHON_EXECUTOR_LISTEN statement (line 188) are not present in the debug file and should be added.

🤖 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 `@gateway/gateway-runtime/docker-entrypoint.sh` at line 24, The debug
entrypoint is out of sync: add the same --py.listen guard logic from
docker-entrypoint.sh (the code that rejects --py.listen overrides) into
docker-entrypoint-debug.sh and also add the unset PYTHON_EXECUTOR_LISTEN cleanup
(the unset PYTHON_EXECUTOR_LISTEN statement) so both entrypoints behave
identically; locate the guard and unset usage in docker-entrypoint.sh and copy
the logic into docker-entrypoint-debug.sh near where command-line args are
parsed and where PYTHON_EXECUTOR_LISTEN is cleaned up.
gateway/gateway-runtime/policy-engine/internal/pythonbridge/client.go (1)

229-236: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply default timeout before Connect to bound blocking dial.

Connect runs before withDefaultTimeout, so grpc.WithBlock() can wait much longer than pythonExecutorTimeout when the caller context has no deadline.

Proposed fix
 func (sm *StreamManager) Execute(ctx context.Context, req *proto.StreamRequest) (*proto.StreamResponse, error) {
+	ctx, cancel := withDefaultTimeout(ctx, getTimeout())
+	defer cancel()
+
 	if !sm.IsConnected() {
 		if err := sm.Connect(ctx); err != nil {
 			return nil, fmt.Errorf("connect Python Executor: %w", err)
 		}
 	}
-
-	ctx, cancel := withDefaultTimeout(ctx, getTimeout())
-	defer cancel()
 
 	if err := ctx.Err(); err != nil {
 		return nil, err
 	}
🤖 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 `@gateway/gateway-runtime/policy-engine/internal/pythonbridge/client.go` around
lines 229 - 236, Move the withDefaultTimeout(ctx, getTimeout()) call to occur
before checking/using sm.IsConnected() and sm.Connect so the dial is bounded by
the default timeout; specifically, obtain the new ctx and cancel via
withDefaultTimeout(ctx, getTimeout()) (and defer cancel) prior to calling
sm.IsConnected() and then call sm.Connect(ctx) when needed, ensuring you pass
the timeout-wrapped ctx into sm.Connect rather than the original caller ctx.
🧹 Nitpick comments (2)
gateway/gateway-runtime/policy-engine/cmd/policy-engine/main.go (1)

301-336: ⚡ Quick win

Close the StreamManager during graceful shutdown.

The shutdown sequence stops every long-lived component (admin, metrics, xDS, ALS, gRPC server) except the Python bridge. The StreamManager holds an open gRPC client connection; closing it explicitly allows the Python executor to observe a clean disconnect rather than a TCP RST or OS-level teardown.

♻️ Suggested addition after `grpcServer.GracefulStop()`
 grpcServer.GracefulStop()
+
+// Close the Python executor bridge connection (if initialized)
+if sm := pythonbridge.GetStreamManager(); sm != nil {
+    if err := sm.Close(); err != nil {
+        slog.WarnContext(ctx, "Error closing Python bridge", "error", err)
+    }
+}
🤖 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 `@gateway/gateway-runtime/policy-engine/cmd/policy-engine/main.go` around lines
301 - 336, The shutdown sequence omits closing the StreamManager, leaving the
Python bridge’s gRPC client connection unclosed; after grpcServer.GracefulStop()
call, call the StreamManager.Close() (or appropriate Close/Shutdown method on
the StreamManager instance) and wait if there is a blocking Wait/Join method
(e.g., StreamManager.Close() and StreamManager.Wait()) so the Python executor
sees a clean disconnect; locate the StreamManager variable/struct used by the
server and ensure you handle nil checks and log any close errors via slog before
proceeding.
gateway/gateway-runtime/policy-engine/internal/pythonbridge/client.go (1)

513-528: ⚡ Quick win

Avoid silent no-op when Init is called after singleton creation.

If GetStreamManager() initializes first, a later Init() updates config globals but cannot reconfigure the existing singleton. Add an explicit guard/log so this state is visible.

Proposed guard
 func Init(cfg config.PythonExecutorConfig) {
 	pythonExecutorTimeout = cfg.Timeout
@@
 	configuredAddress = address
 	configuredIsTCP = isTCP
 
 	streamManagerOnce.Do(func() {
 		globalStreamManager = NewStreamManager(configuredAddress, configuredIsTCP)
 	})
+	if globalStreamManager != nil &&
+		(globalStreamManager.address != configuredAddress || globalStreamManager.isTCP != configuredIsTCP) {
+		slog.Warn("Python executor bridge already initialized; ignoring reconfiguration",
+			"existing_address", globalStreamManager.address,
+			"existing_tcp", globalStreamManager.isTCP,
+			"requested_address", configuredAddress,
+			"requested_tcp", configuredIsTCP,
+		)
+	}
 
 	slog.Info("Python executor bridge initialized",
🤖 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 `@gateway/gateway-runtime/policy-engine/internal/pythonbridge/client.go` around
lines 513 - 528, The Init function currently updates
configuredAddress/configuredIsTCP but does nothing visible if the singleton was
already created by GetStreamManager; add an explicit guard in Init that checks
whether streamManagerOnce has already executed (i.e., whether
globalStreamManager != nil) and log or return an error indicating Init was
called after the StreamManager was created; specifically modify Init to detect
globalStreamManager != nil (or use an atomic/boolean sentinel alongside
streamManagerOnce), and emit a clear slog.Warn/Info message referencing
configuredAddress and configuredIsTCP so callers can see that reconfiguration
was a no-op for the existing StreamManager.
🤖 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 `@gateway/DEBUG_GUIDE.md`:
- Around line 245-249: The markdownlint MD028 error is caused by an empty line
breaking the adjacent blockquote callouts; edit the DEBUG_GUIDE.md block
containing the [!NOTE] and [!WARNING] callouts so there is no blank line between
them (ensure each callout line begins with ">" so the NOTE and WARNING remain
part of the same blockquote sequence), e.g., remove the blank line between the
"> **When to use this:**..." and "> [!WARNING]" lines to restore contiguous
quoting.

In `@gateway/gateway-runtime/policy-engine/internal/pythonbridge/factory.go`:
- Around line 77-78: GetStreamManager() can return nil and calling
sm.InitPolicy(...) panics; add a nil guard after sm := GetStreamManager() to
handle the uninitialized case (e.g., if sm == nil return a descriptive error via
the same return path as InitPolicy uses or construct an appropriate error
response) so InitPolicy is only invoked on a non-nil *StreamManager; reference
GetStreamManager, the local variable sm, and the method InitPolicy when
implementing the guard and error return.

---

Outside diff comments:
In `@gateway/gateway-runtime/docker-entrypoint.sh`:
- Line 24: The debug entrypoint is out of sync: add the same --py.listen guard
logic from docker-entrypoint.sh (the code that rejects --py.listen overrides)
into docker-entrypoint-debug.sh and also add the unset PYTHON_EXECUTOR_LISTEN
cleanup (the unset PYTHON_EXECUTOR_LISTEN statement) so both entrypoints behave
identically; locate the guard and unset usage in docker-entrypoint.sh and copy
the logic into docker-entrypoint-debug.sh near where command-line args are
parsed and where PYTHON_EXECUTOR_LISTEN is cleaned up.

In `@gateway/gateway-runtime/policy-engine/internal/pythonbridge/client.go`:
- Around line 229-236: Move the withDefaultTimeout(ctx, getTimeout()) call to
occur before checking/using sm.IsConnected() and sm.Connect so the dial is
bounded by the default timeout; specifically, obtain the new ctx and cancel via
withDefaultTimeout(ctx, getTimeout()) (and defer cancel) prior to calling
sm.IsConnected() and then call sm.Connect(ctx) when needed, ensuring you pass
the timeout-wrapped ctx into sm.Connect rather than the original caller ctx.

---

Nitpick comments:
In `@gateway/gateway-runtime/policy-engine/cmd/policy-engine/main.go`:
- Around line 301-336: The shutdown sequence omits closing the StreamManager,
leaving the Python bridge’s gRPC client connection unclosed; after
grpcServer.GracefulStop() call, call the StreamManager.Close() (or appropriate
Close/Shutdown method on the StreamManager instance) and wait if there is a
blocking Wait/Join method (e.g., StreamManager.Close() and StreamManager.Wait())
so the Python executor sees a clean disconnect; locate the StreamManager
variable/struct used by the server and ensure you handle nil checks and log any
close errors via slog before proceeding.

In `@gateway/gateway-runtime/policy-engine/internal/pythonbridge/client.go`:
- Around line 513-528: The Init function currently updates
configuredAddress/configuredIsTCP but does nothing visible if the singleton was
already created by GetStreamManager; add an explicit guard in Init that checks
whether streamManagerOnce has already executed (i.e., whether
globalStreamManager != nil) and log or return an error indicating Init was
called after the StreamManager was created; specifically modify Init to detect
globalStreamManager != nil (or use an atomic/boolean sentinel alongside
streamManagerOnce), and emit a clear slog.Warn/Info message referencing
configuredAddress and configuredIsTCP so callers can see that reconfiguration
was a no-op for the existing StreamManager.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 538d1640-e5e1-4687-8600-11580ee75ef2

📥 Commits

Reviewing files that changed from the base of the PR and between dfbb2f1 and d4f0437.

📒 Files selected for processing (15)
  • .vscode/launch.json
  • gateway/DEBUG_GUIDE.md
  • gateway/configs/config-template.toml
  • gateway/gateway-builder/internal/policyengine/generator.go
  • gateway/gateway-builder/templates/plugin_registry.go.tmpl
  • gateway/gateway-runtime/docker-entrypoint.sh
  • gateway/gateway-runtime/policy-engine/cmd/policy-engine/main.go
  • gateway/gateway-runtime/policy-engine/internal/config/config.go
  • gateway/gateway-runtime/policy-engine/internal/config/config_test.go
  • gateway/gateway-runtime/policy-engine/internal/pythonbridge/bridge_test.go
  • gateway/gateway-runtime/policy-engine/internal/pythonbridge/client.go
  • gateway/gateway-runtime/policy-engine/internal/pythonbridge/client_test.go
  • gateway/gateway-runtime/policy-engine/internal/pythonbridge/factory.go
  • gateway/gateway-runtime/python-executor/executor/server.py
  • gateway/gateway-runtime/python-executor/main.py
💤 Files with no reviewable changes (1)
  • gateway/gateway-builder/templates/plugin_registry.go.tmpl

Comment thread gateway/DEBUG_GUIDE.md
Comment on lines +245 to +249
> [!NOTE]
> **When to use this:** You are developing or debugging a Python policy and need to set breakpoints, add print statements, or iterate rapidly without rebuilding Docker images.

> [!WARNING]
> Processes run directly on the host, so Go resolves modules via `go.work`. Local versions of `sdk` and other workspace modules are used instead of the published Go module versions — including any uncommitted or untagged changes. Behavior may differ from a production build.
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix blockquote spacing between adjacent callouts (MD028).

There is a blank line inside the blockquote sequence between NOTE and WARNING, which triggers markdownlint. Remove the blank line or keep it quoted.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 247-247: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🤖 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 `@gateway/DEBUG_GUIDE.md` around lines 245 - 249, The markdownlint MD028 error
is caused by an empty line breaking the adjacent blockquote callouts; edit the
DEBUG_GUIDE.md block containing the [!NOTE] and [!WARNING] callouts so there is
no blank line between them (ensure each callout line begins with ">" so the NOTE
and WARNING remain part of the same blockquote sequence), e.g., remove the blank
line between the "> **When to use this:**..." and "> [!WARNING]" lines to
restore contiguous quoting.

Comment on lines +77 to +78
sm := GetStreamManager()
resp, err := sm.InitPolicy(ctx, req)
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add nil guard on GetStreamManager() to prevent panic.

GetStreamManager() returns nil if pythonbridge.Init() has not been called. Calling sm.InitPolicy(...) on a nil *StreamManager panics. The comment documents the initialization contract, but does not enforce it — any test or integration path that bypasses main() would crash the process.

🛡️ Proposed fix
 sm := GetStreamManager()
+if sm == nil {
+    return nil, fmt.Errorf("python bridge not initialized: call pythonbridge.Init() before GetPolicy()")
+}
 resp, err := sm.InitPolicy(ctx, req)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sm := GetStreamManager()
resp, err := sm.InitPolicy(ctx, req)
sm := GetStreamManager()
if sm == nil {
return nil, fmt.Errorf("python bridge not initialized: call pythonbridge.Init() before GetPolicy()")
}
resp, err := sm.InitPolicy(ctx, req)
🤖 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 `@gateway/gateway-runtime/policy-engine/internal/pythonbridge/factory.go`
around lines 77 - 78, GetStreamManager() can return nil and calling
sm.InitPolicy(...) panics; add a nil guard after sm := GetStreamManager() to
handle the uninitialized case (e.g., if sm == nil return a descriptive error via
the same return path as InitPolicy uses or construct an appropriate error
response) so InitPolicy is only invoked on a non-nil *StreamManager; reference
GetStreamManager, the local variable sm, and the method InitPolicy when
implementing the guard and error return.

@renuka-fernando renuka-fernando merged commit 064a6fe into main May 8, 2026
5 checks passed
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.

[Task]: Debug guide for Python Policy Executor [Task]: TCP gRPC Python Executor Server

3 participants