Skip to content

fix(build): validate Discord package after backend dependency install#128

Merged
zouyonghe merged 15 commits into
AstrBotDevs:mainfrom
zouyonghe:fix/discord-package-validation
May 9, 2026
Merged

fix(build): validate Discord package after backend dependency install#128
zouyonghe merged 15 commits into
AstrBotDevs:mainfrom
zouyonghe:fix/discord-package-validation

Conversation

@zouyonghe
Copy link
Copy Markdown
Member

@zouyonghe zouyonghe commented May 9, 2026

Summary

Fixes #8016.

The PyPI discord stub package (v0.0.2) shadows the real py-cord package because both occupy the discord namespace. When the stub is installed by mistake (e.g. cache pollution or dependency resolution glitch), the Discord adapter fails at runtime with:

module 'discord' has no attribute 'Bot'

This is a packaging/runtime issue specific to the desktop bundle, not a bug in the core Discord adapter code.

Changes

  • Adds a post-install validation step in build-backend.mjs that imports discord and asserts discord.Bot exists.
  • If the stub is present, the build fails early with a clear error message instead of producing a broken runtime bundle.

Impact

  • Only affects the desktop build pipeline (scripts/backend/build-backend.mjs).
  • No changes to core AstrBot code or Discord adapter logic.

Summary by Sourcery

Add a post-install validation step to ensure the correct Discord package is present in the desktop backend runtime before generating the bundle.

Bug Fixes:

  • Fail the desktop backend build early when the PyPI 'discord' stub package or other mis-installed Discord packages are detected instead of producing a broken runtime bundle.

Build:

  • Hook a Python-based Discord package validation script into the backend build pipeline, configurable via an environment-controlled timeout.

github-actions Bot and others added 14 commits April 11, 2026 14:44
* fix: add backend startup heartbeat liveness probe

* fix: tighten startup heartbeat validation

* refactor: centralize startup heartbeat metadata

* fix: surface heartbeat invalidation sooner

* fix: harden startup heartbeat parsing

* fix: warn on stop-time heartbeat failures

* refactor: simplify startup heartbeat control flow

* refactor: flatten readiness heartbeat helpers

* refactor: clarify heartbeat helper responsibilities

* docs: clarify startup heartbeat path coupling

* fix: harden startup heartbeat coordination

* fix: make startup heartbeat checks monotonic

* fix: clean up heartbeat test and exit handling
…#119)

* fix(ci): prepare macOS resources before optional signing

* test(ci): parse macOS workflow steps structurally

* test(ci): parse workflow YAML structurally

* test(ci): relax workflow assertions and install test deps

* test(ci): share workflow test helpers

* test(ci): harden script workflow dependency setup

* fix(ci): setup pnpm before enabling pnpm cache

* test(ci): relax workflow step assertions
* chore(version): sync desktop version to v4.23.0-beta.1

* fix: add backend startup heartbeat liveness probe (AstrBotDevs#114)

* fix: add backend startup heartbeat liveness probe

* fix: tighten startup heartbeat validation

* refactor: centralize startup heartbeat metadata

* fix: surface heartbeat invalidation sooner

* fix: harden startup heartbeat parsing

* fix: warn on stop-time heartbeat failures

* refactor: simplify startup heartbeat control flow

* refactor: flatten readiness heartbeat helpers

* refactor: clarify heartbeat helper responsibilities

* docs: clarify startup heartbeat path coupling

* fix: harden startup heartbeat coordination

* fix: make startup heartbeat checks monotonic

* fix: clean up heartbeat test and exit handling

* fix: default desktop chat transport to websocket

* fix: respect existing desktop transport preference

* fix: harden desktop transport bootstrap

* fix: centralize desktop transport contract

* fix: harden desktop bridge transport injection

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In validateDiscordPackage, consider also checking result.error from spawnSync and including the exit code in the thrown error to distinguish between a Python runtime failure and the assertion failing.
  • The inline Python assertion string is fairly terse; consider expanding it to a more descriptive message (e.g., mentioning py-cord vs discord directly) so that if someone runs the command manually, the failure is self-explanatory even without the Node-side error handling.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `validateDiscordPackage`, consider also checking `result.error` from `spawnSync` and including the exit code in the thrown error to distinguish between a Python runtime failure and the assertion failing.
- The inline Python assertion string is fairly terse; consider expanding it to a more descriptive message (e.g., mentioning `py-cord` vs `discord` directly) so that if someone runs the command manually, the failure is self-explanatory even without the Node-side error handling.

## Individual Comments

### Comment 1
<location path="scripts/backend/build-backend.mjs" line_range="588-591" />
<code_context>
+const validateDiscordPackage = (runtimePython) => {
+  const result = spawnSync(runtimePython.absolute, [
+    '-c',
+    'import discord; assert hasattr(discord, "Bot"), "discord.Bot not found"',
+  ], {
+    encoding: 'utf8',
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid relying on Python `assert` for validation in the subprocess command.

With `-O` or `PYTHONOPTIMIZE` enabled, `assert` statements are removed, so this validation will never run and an invalid `discord` package could pass. Use an explicit conditional and raise (e.g. `SystemExit` or `RuntimeError`) so the check always runs regardless of optimization settings.

```suggestion
  const result = spawnSync(runtimePython.absolute, [
    '-c',
    'import discord, sys; sys.exit("discord.Bot not found") if not hasattr(discord, "Bot") else None',
  ], {
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/backend/build-backend.mjs Outdated
@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from cf48189 to 6771cef Compare May 9, 2026 14:47
@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

@zouyonghe
Copy link
Copy Markdown
Member Author

Thanks for the review. All comments have been addressed in the amended commit:

  1. Avoid assert in subprocess: Replaced the assert with an explicit conditional + sys.exit(), so the check always runs regardless of PYTHONOPTIMIZE.

  2. Check result.error and include exit code: Added a dedicated result.error check before the status check, and included the exit code in both error messages to distinguish Python runtime failures from assertion failures.

  3. Descriptive Python error message: The inline Python script now outputs a self-explanatory message mentioning the PyPI discord stub vs py-cord, so running the command manually also makes the failure obvious.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

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 introduces a validation step in the backend build process to ensure the correct Discord library (py-cord) is installed. It adds a validateDiscordPackage function that runs a Python check and integrates it into the main build sequence. Feedback suggests enhancing the spawnSync implementation by handling execution errors, utilizing a consistent environment, and improving error message clarity.

Comment thread scripts/backend/build-backend.mjs Outdated
Comment on lines +588 to +602
const result = spawnSync(runtimePython.absolute, [
'-c',
'import discord, sys; ' +
'has_bot = hasattr(discord, "Bot"); ' +
'sys.exit("The installed \"discord\" package is the PyPI stub (not py-cord). "' +
'"discord.Bot is missing. Please clean pip cache and reinstall py-cord.") ' +
'if not has_bot else None',
], {
encoding: 'utf8',
cwd: outputDir,
windowsHide: true,
});
if (result.error) {
throw new Error(
`Discord package validation could not run (exit code ${result.status ?? 'unknown'}): ` +
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

The spawnSync call should handle potential execution errors (via result.error) and use a consistent environment by including createPythonInstallEnv(). Additionally, trimming the output and providing the exit code in the error message improves debuggability, aligning with the error handling patterns used in other parts of the build system (e.g., in runtime-core-lock.mjs).

  const result = spawnSync(runtimePython.absolute, [
    '-c',
    'import discord; assert hasattr(discord, "Bot"), "discord.Bot not found"',
  ], {
    encoding: 'utf8',
    stdio: ['ignore', 'pipe', 'pipe'],
    cwd: outputDir,
    env: createPythonInstallEnv(),
    windowsHide: true,
  });

  if (result.error) {
    throw new Error('Discord package validation failed to start: ' + result.error.message);
  }

  if (result.status !== 0) {
    const detail = result.stderr?.trim() || result.stdout?.trim() || ('exit code ' + result.status);
    throw new Error(
      'Discord package validation failed: ' + detail + '. ' +
      'This usually means the PyPI "discord" stub package was installed instead of "py-cord". ' +
      'Please clean the build environment (pip cache, virtualenv) and retry.'
    );
  }

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The inline Python one-liner in validateDiscordPackage is quite dense and relies on heavy quoting/concatenation; consider extracting it into a separate multi-line string constant (or even a small helper script file) to improve readability and reduce the risk of subtle escaping issues.
  • In validateDiscordPackage, you only check for the presence of discord.Bot; if feasible, you might also assert the expected package origin (e.g., via discord.__file__ or discord.__package__) to make the validation more robust against other unexpected discord implementations.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The inline Python one-liner in `validateDiscordPackage` is quite dense and relies on heavy quoting/concatenation; consider extracting it into a separate multi-line string constant (or even a small helper script file) to improve readability and reduce the risk of subtle escaping issues.
- In `validateDiscordPackage`, you only check for the presence of `discord.Bot`; if feasible, you might also assert the expected package origin (e.g., via `discord.__file__` or `discord.__package__`) to make the validation more robust against other unexpected `discord` implementations.

## Individual Comments

### Comment 1
<location path="scripts/backend/build-backend.mjs" line_range="600-603" />
<code_context>
+    cwd: outputDir,
+    windowsHide: true,
+  });
+  if (result.error) {
+    throw new Error(
+      `Discord package validation could not run (exit code ${result.status ?? 'unknown'}): ` +
+      `${result.error.message}. This may indicate a Python runtime issue.`,
+    );
+  }
</code_context>
<issue_to_address>
**suggestion:** Include additional spawn diagnostics (signal/stdout/stderr) when the process errors to aid debugging.

When `spawnSync` returns with `result.error`, `status` is often `null` while `signal` and/or `stdout`/`stderr` still carry useful context. Currently we only log `status` and `error.message`, which can obscure issues like `ENOENT`, permissions, or signals. Please include `result.signal` and any available `stdout`/`stderr` in this error path to make failures (e.g. in CI) easier to diagnose.

Suggested implementation:

```javascript
  const result = spawnSync(runtimePython.absolute, [
    '-c',
    'import discord, sys; ' +
    'has_bot = hasattr(discord, "Bot"); ' +
    'sys.exit("The installed \"discord\" package is the PyPI stub (not py-cord). "' +
    '"discord.Bot is missing. Please clean pip cache and reinstall py-cord.") ' +
    'if not has_bot else None',

```

```javascript
  if (result.error) {
    const details = [
      `exit status: ${result.status ?? 'unknown'}`,
      result.signal ? `signal: ${result.signal}` : null,
      result.stdout ? `stdout: ${String(result.stdout).trim()}` : null,
      result.stderr ? `stderr: ${String(result.stderr).trim()}` : null,
    ]
      .filter(Boolean)
      .join('; ');

    throw new Error(
      `Discord package validation could not run. This may indicate a Python runtime issue. ` +
      `Details: ${details}. Underlying error: ${result.error.message}`,
    );
  }

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/backend/build-backend.mjs Outdated
@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from 6771cef to 635cc8d Compare May 9, 2026 14:51
@zouyonghe
Copy link
Copy Markdown
Member Author

Thanks for the review. Changes applied:

  1. Extracted Python validation script into a multi-line constant: DISCORD_VALIDATION_SCRIPT is now a dedicated template string, improving readability and eliminating the dense one-liner with heavy quoting/concatenation.

  2. Added discord.__file__ to the validation output: The script now prints the resolved package file path in the error message, making it easier to diagnose which exact discord package is installed.

  3. Enhanced result.error diagnostics: Adopted the suggested implementation—when spawnSync returns result.error, the error now includes result.signal and any available stdout/stderr, in addition to the exit status. This makes CI/runtime failures (e.g. ENOENT, permissions, signals) much easier to diagnose.

@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Consider scoping validateDiscordPackage so it only runs when the Discord adapter/runtime is actually enabled for the build, to avoid failing builds that don’t ship Discord support at all.
  • The Python DISCORD_VALIDATION_SCRIPT embeds a fairly detailed error message string; you might want to centralize the user-facing guidance in the Node side to avoid maintaining similar text in two places and to keep localization/consistency simpler.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider scoping `validateDiscordPackage` so it only runs when the Discord adapter/runtime is actually enabled for the build, to avoid failing builds that don’t ship Discord support at all.
- The Python `DISCORD_VALIDATION_SCRIPT` embeds a fairly detailed error message string; you might want to centralize the user-facing guidance in the Node side to avoid maintaining similar text in two places and to keep localization/consistency simpler.

## Individual Comments

### Comment 1
<location path="scripts/backend/build-backend.mjs" line_range="634-641" />
<code_context>
+
+  if (result.status !== 0) {
+    throw new Error(
+      `Discord package validation failed (exit code ${result.status}): ` +
+      `${result.stderr || result.stdout || 'unknown error'}. ` +
+      'This usually means the PyPI "discord" stub package was installed instead of "py-cord". ' +
+      'Please clean the build environment (pip cache, virtualenv) and retry.',
</code_context>
<issue_to_address>
**suggestion:** Normalize stderr/stdout output to avoid noisy formatting in error messages.

Because `stderr`/`stdout` may include trailing newlines or extra spacing, consider trimming them before interpolation to keep the error message clean, e.g.:

```js
const stderr = result.stderr?.trim();
const stdout = result.stdout?.trim();
...
`${stderr || stdout || 'unknown error'}. ` +
```

```suggestion
  if (result.status !== 0) {
    const stderr = result.stderr?.trim();
    const stdout = result.stdout?.trim();

    throw new Error(
      `Discord package validation failed (exit code ${result.status}): ` +
      `${stderr || stdout || 'unknown error'}. ` +
      'This usually means the PyPI "discord" stub package was installed instead of "py-cord". ' +
      'Please clean the build environment (pip cache, virtualenv) and retry.',
    );
  }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/backend/build-backend.mjs Outdated
@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from 635cc8d to c4c497c Compare May 9, 2026 14:58
@zouyonghe
Copy link
Copy Markdown
Member Author

Thanks for the review. Changes applied:

  1. Trimmed stderr/stdout before interpolation: Added result.stderr?.trim() and result.stdout?.trim() before using them in error messages to avoid trailing newlines and extra spacing.

  2. Centralized user-facing guidance in Node layer: The Python script now outputs a minimal JSON blob ({ok, file, missing}) with no user-facing text. The Node side parses this JSON and composes the full human-readable error message. This keeps all localization and guidance logic in one place.

  3. Why validate on every build: AstrBot is designed as a general-purpose runtime where platform adapters are loaded dynamically at runtime based on user config. The desktop backend bundle therefore always includes all adapter code and their dependencies (including py-cord from requirements.txt). Validating that the installed discord package is the real py-cord rather than the PyPI stub protects every build from producing a broken runtime, regardless of which adapters the end user enables.

@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The embedded Python script in DISCORD_VALIDATION_SCRIPT is getting fairly complex; consider moving it into a separate .py helper file so that error handling and future validation logic remain easier to maintain and edit without touching the build script.
  • In validateDiscordPackage, JSON parsing assumes stdout contains only the JSON payload; if other output (e.g., warnings) sneaks in, parsing will fail silently—consider constraining the Python script to only print JSON or adding a more robust way to locate the JSON segment in stdout.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The embedded Python script in `DISCORD_VALIDATION_SCRIPT` is getting fairly complex; consider moving it into a separate `.py` helper file so that error handling and future validation logic remain easier to maintain and edit without touching the build script.
- In `validateDiscordPackage`, JSON parsing assumes `stdout` contains only the JSON payload; if other output (e.g., warnings) sneaks in, parsing will fail silently—consider constraining the Python script to only print JSON or adding a more robust way to locate the JSON segment in `stdout`.

## Individual Comments

### Comment 1
<location path="scripts/backend/build-backend.mjs" line_range="587-595" />
<code_context>
   }
 };

+const DISCORD_VALIDATION_SCRIPT = `
+import discord, json, sys
+
+result = {"ok": True, "file": getattr(discord, "__file__", None)}
+if not hasattr(discord, "Bot"):
+    result["ok"] = False
+    result["missing"] = ["discord.Bot"]
+print(json.dumps(result))
+sys.exit(0 if result["ok"] else 1)
+`;
+
</code_context>
<issue_to_address>
**suggestion:** Handle ImportError in the Python validation script to provide clearer diagnostics when `discord` is not installed at all.

Right now, if `import discord` raises ImportError, the script exits before emitting JSON, so the JS side only sees generic stderr/stdout. To keep structured diagnostics even in this case, wrap the import in try/except and always emit a JSON result, e.g.:

```python
import json, sys

result = {"ok": False, "missing": ["discord"], "file": None}
try:
    import discord
    result["file"] = getattr(discord, "__file__", None)
    if not hasattr(discord, "Bot"):
        result["missing"] = ["discord.Bot"]
    else:
        result["ok"] = True
except ImportError:
    pass

print(json.dumps(result))
sys.exit(0 if result["ok"] else 1)
```

This preserves the current Bot check while adding a clear signal when the package is missing entirely.
</issue_to_address>

### Comment 2
<location path="scripts/backend/build-backend.mjs" line_range="619-622" />
<code_context>
+      .filter(Boolean)
+      .join('; ');
+
+    throw new Error(
+      'Discord package validation could not run. ' +
+      `This may indicate a Python runtime issue. Details: ${details}. ` +
+      `Underlying error: ${result.error.message}`,
+    );
+  }
</code_context>
<issue_to_address>
**suggestion:** Include interpreter identity in the error message when the validation spawn fails.

Since multiple Python runtimes may be present on the build machine, please include which executable was used (e.g., `runtimePython.absolute` or a short label) in this error message so we can quickly identify misconfigured or incompatible runtimes when validation spawn fails.

Suggested implementation:

```javascript
  if (result.error) {
    const details = [
      `exit status: ${result.status ?? 'unknown'}`,
      result.signal ? `signal: ${result.signal}` : null,
      result.stdout ? `stdout: ${String(result.stdout).trim()}` : null,
      result.stderr ? `stderr: ${String(result.stderr).trim()}` : null,
    ]
      .filter(Boolean)
      .join('; ');

    const interpreterLabel =
      (runtimePython && ('label' in runtimePython) && runtimePython.label) ||
      (runtimePython && ('absolute' in runtimePython) && runtimePython.absolute) ||
      'unknown Python interpreter';

    throw new Error(
      'Discord package validation could not run. ' +
      `Interpreter: ${interpreterLabel}. ` +
      `This may indicate a Python runtime issue. Details: ${details}. ` +
      `Underlying error: ${result.error.message}`,
    );
  }

```

These changes assume a `runtimePython` object is already available in this scope with either a `label` or `absolute` property (matching the rest of your build script). If that is not yet the case, you should:
1. Ensure the value returned from your Python runtime detection logic is assigned to a variable named `runtimePython` in the same scope as this error handling block.
2. Confirm that the object includes a `label` (e.g., "system Python 3.11") and/or `absolute` (full path to the interpreter) property so the error message can precisely identify the interpreter.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/backend/build-backend.mjs Outdated
Comment thread scripts/backend/build-backend.mjs
@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from c4c497c to d8b901e Compare May 9, 2026 15:05
@zouyonghe
Copy link
Copy Markdown
Member Author

Thanks for the review. Changes applied:

  1. Extracted Python script to scripts/backend/tools/validate_discord.py: The validation logic is now in a dedicated .py file, matching the existing scan_imports.py pattern and making future maintenance easier.

  2. Handled ImportError in the Python script: The script now wraps import discord in a try/except ImportError and always emits a structured JSON result, so missing packages produce clear diagnostics instead of raw stderr.

  3. Robust JSON extraction in Node layer: Instead of parsing the entire stdout, the Node side now uses a regex /\{[\s\S]*\}/ to extract the JSON payload. This tolerates stray warnings or logs that might be printed before/after the JSON.

  4. Included interpreter identity in spawn error messages: The result.error branch now includes runtimePython.absolute in the error message, making it easy to identify misconfigured or incompatible Python runtimes in CI.

@zouyonghe
Copy link
Copy Markdown
Member Author

Thanks for the detailed review. Changes applied:

  1. Tightened JSON contract in validate_discord.py: The script now prints exactly one JSON object to stdout and nothing else. A comment enforces this contract. Any future debug logging should go to stderr, not stdout.

  2. Added discord.__version__ to the validation payload: The JSON now includes version, which makes it trivial to distinguish the PyPI stub (0.0.2) from py-cord (2.x.y).

  3. Removed regex-based JSON extraction in Node: validateDiscordPackage now uses a strict JSON.parse(result.stdout) directly, with a single try/catch fallback to raw stderr/stdout. The branching is collapsed into a clean linear flow:

    • result.error → runtime issue
    • result.status === 0 → success
    • otherwise → parse JSON or fallback to raw output

@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The JSON protocol between validate_discord.py and validateDiscordPackage assumes that nothing else writes to stdout, but imports (or environment hooks) can emit unexpected output; consider making the script write JSON to stdout only via a clear delimiter or by forcing other messages to stderr so the Node side can reliably parse it.
  • To avoid build hangs if the runtime Python process blocks while running validate_discord.py, consider adding a timeout to the spawnSync call in validateDiscordPackage and handling that case explicitly in the error message.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The JSON protocol between `validate_discord.py` and `validateDiscordPackage` assumes that nothing else writes to stdout, but imports (or environment hooks) can emit unexpected output; consider making the script write JSON to stdout only via a clear delimiter or by forcing other messages to stderr so the Node side can reliably parse it.
- To avoid build hangs if the runtime Python process blocks while running `validate_discord.py`, consider adding a `timeout` to the `spawnSync` call in `validateDiscordPackage` and handling that case explicitly in the error message.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from 43a7ceb to 77cef58 Compare May 9, 2026 15:29
@zouyonghe
Copy link
Copy Markdown
Member Author

Thanks for the review. Changes applied:

  1. Added known JSON prefix to tolerate stdout pollution: validate_discord.py now emits a single prefixed line ASTRBOT_VALIDATE_DISCORD_JSON:{...} to stdout. Node side uses extractPrefixedJson() to scan stdout line-by-line and find the one starting with the prefix, then strips the prefix and parses the JSON. This makes the protocol robust against import-time stdout pollution from environment hooks or third-party packages.

  2. Added spawnSync timeout and explicit timeout handling: Set a 30-second timeout (DISCORD_VALIDATION_TIMEOUT_MS = 30_000) on the spawnSync call. When result.error.code === 'ETIMEDOUT', the error message clearly says "Timed out after 30000ms" instead of the generic runtime issue text, making CI hangs easier to diagnose.

@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The ASTRBOT_VALIDATE_DISCORD_JSON prefix string is duplicated in both build-backend.mjs and validate_discord.py; consider centralizing it (or at least adding a shared comment) so future changes don’t accidentally desynchronize the producer/consumer formats.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `ASTRBOT_VALIDATE_DISCORD_JSON` prefix string is duplicated in both `build-backend.mjs` and `validate_discord.py`; consider centralizing it (or at least adding a shared comment) so future changes don’t accidentally desynchronize the producer/consumer formats.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from 77cef58 to 0e3543b Compare May 9, 2026 15:34
@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

@zouyonghe
Copy link
Copy Markdown
Member Author

Good catch. Added cross-reference comments in both files:

  • validate_discord.py: notes that its hard-coded prefix must stay in sync with DISCORD_JSON_PREFIX in build-backend.mjs.
  • build-backend.mjs: notes that DISCORD_JSON_PREFIX must stay in sync with the hard-coded prefix in validate_discord.py.

Since the two files are in different languages (Python vs. JavaScript), a shared constant file isn't practical here; the comments serve as the lightweight coupling contract.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The DISCORD_JSON_PREFIX value is duplicated and manually kept in sync between build-backend.mjs and validate_discord.py; consider passing it via an environment variable or CLI argument so the prefix is defined in one place and avoids future drift.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `DISCORD_JSON_PREFIX` value is duplicated and manually kept in sync between `build-backend.mjs` and `validate_discord.py`; consider passing it via an environment variable or CLI argument so the prefix is defined in one place and avoids future drift.

## Individual Comments

### Comment 1
<location path="scripts/backend/tools/validate_discord.py" line_range="18-19" />
<code_context>
+        result["missing"] = []
+except ImportError:
+    pass
+except Exception as e:
+    result["error"] = str(e)
+
+# Emit a single prefixed JSON line on stdout so the caller can reliably
</code_context>
<issue_to_address>
**suggestion:** Capture slightly richer exception info to aid diagnosing unexpected import-time failures.

`str(e)` drops useful context like the exception type. Consider including the class name or a repr (e.g. `f"{type(e).__name__}: {e}"`) so `validateDiscordPackage` surfaces more actionable errors for unexpected import-time failures.

```suggestion
except Exception as e:
    result["error"] = f"{type(e).__name__}: {e}"
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/backend/tools/validate_discord.py Outdated
@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from 0e3543b to b39e280 Compare May 9, 2026 15:39
@zouyonghe
Copy link
Copy Markdown
Member Author

Thanks for the review. Changes applied:

  1. Passed prefix via CLI argument: build-backend.mjs now passes DISCORD_JSON_PREFIX as sys.argv[1] to validate_discord.py. The prefix is defined in exactly one place (the JS constant), eliminating any risk of drift. The Python script falls back to the literal string if run standalone without arguments.

  2. Richer exception info: except Exception now captures f\"{type(e).__name__}: {e}\" instead of plain str(e), preserving the exception class name for more actionable diagnostics.

Both cross-reference comments have been removed since the hard-coded sync is no longer needed.

@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In validate_discord.py, when an ImportError occurs you currently just leave the default missing value; consider recording the actual exception message (similar to the generic Exception branch) to provide clearer diagnostics when the module cannot be imported at all.
  • The DISCORD_JSON_PREFIX constant is effectively duplicated between build-backend.mjs and the default prefix in validate_discord.py; consider deriving the prefix solely from the CLI argument (and removing the hardcoded default/value in the script) to avoid these two strings drifting out of sync.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `validate_discord.py`, when an `ImportError` occurs you currently just leave the default `missing` value; consider recording the actual exception message (similar to the generic `Exception` branch) to provide clearer diagnostics when the module cannot be imported at all.
- The `DISCORD_JSON_PREFIX` constant is effectively duplicated between `build-backend.mjs` and the default prefix in `validate_discord.py`; consider deriving the prefix solely from the CLI argument (and removing the hardcoded default/value in the script) to avoid these two strings drifting out of sync.

## Individual Comments

### Comment 1
<location path="scripts/backend/build-backend.mjs" line_range="615-624" />
<code_context>
+
+  if (result.error) {
+    const isTimeout = result.error.code === 'ETIMEDOUT';
+    const details = [
+      `exit status: ${result.status ?? 'unknown'}`,
+      result.signal ? `signal: ${result.signal}` : null,
+      result.stdout ? `stdout: ${String(result.stdout).trim()}` : null,
+      result.stderr ? `stderr: ${String(result.stderr).trim()}` : null,
+    ]
+      .filter(Boolean)
+      .join('; ');
+
+    throw new Error(
</code_context>
<issue_to_address>
**suggestion:** Include the spawnSync error code in the diagnostic details to aid debugging.

`details` currently omits `result.error.code`, so failures due to `error` (e.g., ETIMEDOUT vs ENOENT) look the same in logs aside from the trailing "Underlying error" text. Please add the error code to `details` (e.g., `error code: ${result.error.code}`) so we can distinguish timeout from other failures directly from the main diagnostic string.

```suggestion
  if (result.error) {
    const isTimeout = result.error.code === 'ETIMEDOUT';
    const details = [
      `exit status: ${result.status ?? 'unknown'}`,
      `error code: ${result.error.code ?? 'unknown'}`,
      result.signal ? `signal: ${result.signal}` : null,
      result.stdout ? `stdout: ${String(result.stdout).trim()}` : null,
      result.stderr ? `stderr: ${String(result.stderr).trim()}` : null,
    ]
      .filter(Boolean)
      .join('; ');
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread scripts/backend/build-backend.mjs
@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from b39e280 to f6521af Compare May 9, 2026 15:43
@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

@zouyonghe
Copy link
Copy Markdown
Member Author

All three points addressed:

  1. ImportError now records exception info: except ImportError as e now captures f\"{type(e).__name__}: {e}\" into result[\"error\"], same as the generic Exception branch. Missing discord now produces a clear diagnostic instead of silently falling back to the default missing message.

  2. Removed fallback prefix default in Python: validate_discord.py now requires sys.argv[1] without fallback. The prefix is defined in exactly one place (DISCORD_JSON_PREFIX in build-backend.mjs).

  3. Added error code to spawn diagnostics: result.error.code is now included in the details array, making it easy to distinguish ETIMEDOUT from ENOENT or other spawn failures directly from the diagnostic string.

Copy link
Copy Markdown

@SourceryAI SourceryAI left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In validate_discord.py, consider handling the case where sys.argv[1] is missing (e.g., defaulting to an empty prefix or raising a clearer error) to avoid an IndexError if the script is invoked incorrectly.
  • In validateDiscordPackage, you currently rely on stdout parsing for diagnostics; it might be useful to log the raw stdout/stderr at a debug level when JSON extraction fails to make troubleshooting unexpected failures easier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `validate_discord.py`, consider handling the case where `sys.argv[1]` is missing (e.g., defaulting to an empty prefix or raising a clearer error) to avoid an IndexError if the script is invoked incorrectly.
- In `validateDiscordPackage`, you currently rely on stdout parsing for diagnostics; it might be useful to log the raw stdout/stderr at a debug level when JSON extraction fails to make troubleshooting unexpected failures easier.

Hi @zouyonghe! 👋

Thanks for trying out Sourcery by commenting with @sourcery-ai review! 🚀

Install the sourcery-ai bot to get automatic code reviews on every pull request ✨

Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In extractPrefixedJson, consider splitting on a regex like /\r?\n/ to handle Windows-style line endings more robustly when parsing the prefixed JSON line from stdout.
  • In validate_discord.py, accessing sys.argv[1] will raise an IndexError if the script is executed manually without arguments; adding a default prefix or a short usage guard would make the helper more robust outside the build pipeline.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `extractPrefixedJson`, consider splitting on a regex like `/\r?\n/` to handle Windows-style line endings more robustly when parsing the prefixed JSON line from stdout.
- In `validate_discord.py`, accessing `sys.argv[1]` will raise an IndexError if the script is executed manually without arguments; adding a default prefix or a short usage guard would make the helper more robust outside the build pipeline.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from f6521af to 99d91da Compare May 9, 2026 15:48
@zouyonghe
Copy link
Copy Markdown
Member Author

Quick polish applied:

  1. Windows line endings in extractPrefixedJson: Changed split('\\n') to split(/\\r?\\n/) so \\r\\n lines are handled correctly on Windows.

  2. Standalone-friendly validate_discord.py: Restored a default prefix (ASTRBOT_VALIDATE_DISCORD_JSON:) when argv[1] is absent, so running the script manually for a quick check doesn't crash with an IndexError.

@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Consider making the Discord validation timeout configurable (e.g., via env or a shared constant near other build timeouts) so slower CI environments can adjust it without code changes.
  • The error message in validateDiscordPackage assumes the PyPI discord stub is the issue; you might want to slightly generalize this text or include a hint that other mis-installs could also cause the validation to fail, while still mentioning the stub case explicitly.
  • Since validate_discord.py is tied closely to the JSON prefix protocol, it could be helpful to add a brief comment in build-backend.mjs near DISCORD_JSON_PREFIX referencing this script so future refactors keep the contract between the JS and Python consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider making the Discord validation timeout configurable (e.g., via env or a shared constant near other build timeouts) so slower CI environments can adjust it without code changes.
- The error message in `validateDiscordPackage` assumes the PyPI `discord` stub is the issue; you might want to slightly generalize this text or include a hint that other mis-installs could also cause the validation to fail, while still mentioning the stub case explicitly.
- Since `validate_discord.py` is tied closely to the JSON prefix protocol, it could be helpful to add a brief comment in `build-backend.mjs` near `DISCORD_JSON_PREFIX` referencing this script so future refactors keep the contract between the JS and Python consistent.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

The PyPI 'discord' stub package (v0.0.2) shadows the real 'py-cord'
package because both occupy the 'discord' namespace. When the stub is
installed by mistake (e.g. cache pollution or dependency resolution
glitch), the Discord adapter fails at runtime with:
  module 'discord' has no attribute 'Bot'

This change adds a post-install validation step that imports 'discord'
and asserts 'discord.Bot' exists. If the stub is present, the build
fails early with a clear error message instead of producing a broken
runtime bundle.

Closes #8016
@zouyonghe zouyonghe force-pushed the fix/discord-package-validation branch from 99d91da to 893aa94 Compare May 9, 2026 15:52
@zouyonghe
Copy link
Copy Markdown
Member Author

Quick polish applied:

  1. Configurable timeout: DISCORD_VALIDATION_TIMEOUT_MS now reads from ASTRBOT_DISCORD_VALIDATION_TIMEOUT env var and falls back to 30s, so slower CI environments can adjust without code changes.

  2. Generalized error message: The failure message now says "The most common cause is the PyPI 'discord' stub... but other mis-installs can also trigger this", acknowledging that the stub is the primary culprit but not the only possible cause.

  3. Added cross-reference comment: A comment above DISCORD_JSON_PREFIX now references scripts/backend/tools/validate_discord.py to remind future refactors that the protocol is shared between the two files.

@zouyonghe
Copy link
Copy Markdown
Member Author

@sourcery-ai review

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@zouyonghe zouyonghe merged commit 69ca5fa into AstrBotDevs:main May 9, 2026
4 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.

2 participants