Error Handling Mechanisms in main.py: How qiaomu-anything-to-notebooklm Manages Failures

The qiaomu-anything-to-notebooklm tool implements robust error handling in main.py through subprocess return-code validation, explicit exception catching, retry loops with back-off, and graceful exits via sys.exit(1) to ensure clear failure reporting when external commands fail.

main.py serves as the entry point for the qiaomu-anything-to-notebooklm project, orchestrating interactions with external commands like notebooklm and lark-cli while parsing JSON and handling network operations. Understanding the error handling mechanisms in main.py is essential for debugging integration failures with NotebookLM and Feishu workflows. The codebase employs a fail-fast strategy that validates subprocess results, catches specific exceptions, and aborts with descriptive messages when recovery is impossible.

Subprocess Return-Code Validation

Validating External Command Execution

After every subprocess.run call, the code explicitly checks result.returncode to detect failures before proceeding. This pattern appears in upload_to_notebooklm (lines 71-84) and create_feishu_doc (lines 25-38), where non-zero return codes trigger immediate error reporting to stderr.

result = subprocess.run(
    ['notebooklm', 'create', title],
    capture_output=True,
    text=True
)
if result.returncode != 0:
    print(f"❌ 创建笔记本失败: {result.stderr}", file=sys.stderr)
    return False

Graceful Early Exit Strategy

Immediate Termination on Critical Failures

When essential steps fail—such as notebook creation, transcript downloads, or JSON parsing—the script prints detailed error messages and calls sys.exit(1) to halt execution. This pattern appears throughout main() when a notebooklm call fails (lines 80-83), after a failed transcript fetch (lines 75-78), and when detecting unsupported input types (lines 104-106).

else:
    print(f"❌ 不支持的输入类型: {input_type}", file=sys.stderr)
    print("提示: 请使用 EPUB、PDF、TXT、MD 文件或 URL", file=sys.stderr)
    sys.exit(1)

Retry Mechanisms with Back-off

Resilient Query Handling in ask_notebooklm

The ask_notebooklm function implements a retry loop (lines 84-103) that catches non-zero return codes and short answers, attempting recovery up to a configurable maximum (default 1 retry). Between attempts, the code sleeps for 2 seconds (lines 99-101) to prevent rapid-fire failures.

def ask_notebooklm(question, max_retries=1):
    for attempt in range(max_retries + 1):
        result = subprocess.run(
            ['notebooklm', 'ask', question],
            capture_output=True,
            text=True
        )
        if result.returncode == 0 and result.stdout.strip():
            return result.stdout.strip()
        if attempt < max_retries:
            print("  重试中...", end=" ")
            time.sleep(2)          # simple back‑off

    print(f"⚠️ 提问失败(已重试{max_retries}次)", file=sys.stderr)
    return None

JSON Decoding Safety

Protected JSON Parsing

When processing transcript data, the output is wrapped in a try/except json.JSONDecodeError block (lines 381-384). If parsing fails, the raw output is logged to stderr before the script aborts, providing debug context without crashing silently.

try:
    data = json.loads(result.stdout.strip())
except json.JSONDecodeError:
    print(f"❌ 解析输出失败: {result.stdout}", file=sys.stderr)
    sys.exit(1)

Input Validation and File Safety

Proactive File Existence Checks

The detect_input_type function uses Path(input_path).exists() (lines 30-33) to verify file availability before operations, preventing FileNotFoundError exceptions during processing.

Input Type Validation

The script validates input types (EPUB, PDF, TXT, MD, URL) and prints user-friendly errors when encountering unsupported formats (lines 104-106).

Filename Sanitization

When creating temporary files for X/Twitter posts, the code uses regular expressions to strip unsafe characters (lines 29-32), preventing path-related injection vulnerabilities.

While main.py contains the primary logic, similar patterns appear in supporting files according to the repository structure. The check_env.py script demonstrates environment-related validation, while scripts/get_podcast_transcript.py handles network failures that propagate to the main flow. The Feishu integration module in feishu-read-mcp/ mirrors the subprocess return-code approach used for lark-cli calls.

Summary

  • Subprocess return-code checks after every external command execution ensure immediate failure detection in functions like upload_to_notebooklm.
  • Graceful exits via sys.exit(1) prevent partial state when critical operations fail, with clear error messages printed to stderr.
  • Retry loops with 2-second delays provide resilience for NotebookLM queries in ask_notebooklm.
  • Try/except blocks around json.JSONDecodeError protect against malformed API responses from transcript scripts.
  • Proactive file existence checks and input validation prevent common runtime errors before file operations begin.
  • Regex-based filename sanitization blocks path traversal attempts when handling social media content.

Frequently Asked Questions

What happens when the notebooklm command fails?

When the notebooklm command returns a non-zero exit code in upload_to_notebooklm (lines 71-84), the script prints the stderr output with a clear error message and returns False. If this occurs during the main workflow, the script subsequently calls sys.exit(1) to terminate execution rather than continuing with an invalid state.

How does main.py handle invalid JSON responses?

The code wraps JSON parsing in a try/except block that catches json.JSONDecodeError (lines 381-384). When triggered, it prints the raw stdout content to stderr and exits with status 1, allowing users to see exactly what malformed data was received from external scripts like get_podcast_transcript.py.

Why does the script use sys.exit(1) instead of raising exceptions?

Using sys.exit(1) provides immediate, predictable termination with a non-zero status code that signals failure to calling processes and CI/CD pipelines. This approach ensures the tool fails loudly with explanatory messages printed to stderr, rather than propagating uncaught exceptions or returning incomplete results.

How many retry attempts does ask_notebooklm perform by default?

The ask_notebooklm function defaults to 1 retry attempt (for a total of 2 tries), with a 2-second sleep delay between attempts (lines 99-101). This default balances resilience against temporary network issues with avoiding excessive delays in the user workflow.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →