While implementing a CLI command for a monitoring tool, I encountered strange behavior.

Running python -m monitoring_sdk.core.cli metabase-check in Jenkins produced no errors and exited with code 0. But the expected processing didn't execute at all. No report files were generated.

+ python -m monitoring_sdk.core.cli metabase-check --config config.yaml --output reports
+ echo === Check completed ===
=== Check completed ===
+ ls -la reports/
total 0
drwxr-xr-x. 2 root root  40 Jan 28 02:23 .

Normal exit, yet nothing happened. This "silent failure" plagued me for over an hour.

Honestly, I didn't expect to get stuck on such a basic problem.


Related Books (For Learning)

For understanding Python basics and execution mechanisms that appeared in this example:

[📦 商品リンク: moshimo-card-EdE1y]

"Minna no Python (Everyone's Python) 5th Edition" provides thorough explanations from Python fundamentals to module design and execution methods. It helps prevent pitfalls around CLI implementation.

Also recommended for those wanting to solidify Python basics as a foundation for AI utilization.


Speed and Fragility of AI-Driven Development

This project was developed at considerable speed using AI. CLI command implementation, log configuration, monitoring logic, report generation. The code itself was written quickly.

But looking back, I kept writing code with the assumption "it'll work" without proper review. Tests were postponed. Actually running it came at the very end.

Then I hit this problem in the testing phase.

The coding speed itself was certainly fast. But when hitting such troubles, it's surprisingly fragile. Unable to identify the cause, I chased logs, formulated hypotheses, and repeated verification. Before I knew it, over an hour had passed.

Initial Hypothesis: Log Output Issue?

Initially, I suspected Python's standard output buffering. I tried adding the python -u flag to the Jenkinsfile to disable buffering, but realized "wouldn't strengthening Python-side log output be better?" and changed direction.

I added loggers to each module to output INFO logs at each processing stage.

from monitoring_sdk.utils.logging_config import get_logger

logger = get_logger(__name__)

def metabase_check(config: str, output: str):
    logger.info("Starting metabase-check command: config=%s, output=%s", config, output)
    # ... processing ...
    logger.info("metabase-check command completed")

※ From here, this becomes a story about development experience.

I decided to add log output, but the target was 32 files. All files related to monitors, CLI commands, reporters, and HTTP API.

If done manually, it would take half a day. Open the file, add import statements, add logs to each function, verify operation... repeat 32 times.

But this time was different. Because I used DevLoop Runner, which I'm developing. This is a tool that automatically takes you from issue to PR.

issue: "Add get_logger log output to all 32 files in monitoring_sdk"
↓
Automatic code modification (about 2 hours)
↓
PR creation

The actual time taken was about 2 hours. But I only manually worked for 3-5 minutes on issue creation and job execution. During the remaining 2 hours, I could work on other tasks while the automated processing ran.

Where manual work would require being glued to the task for half a day, the actual constraint time is just 5 minutes. This difference is significant.

Log additions for 32 files completed and merged. Debug logs were now comprehensive.

Unnatural Behavior Revealed by Debug Logs

I raised the log level to DEBUG and re-executed. Then this output appeared:

2026-01-28 09:01:37,029 - monitoring_sdk.core - DEBUG - monitoring_sdk.core package initialized
2026-01-28 09:01:37,066 - monitoring_sdk.monitors - DEBUG - monitoring_sdk.monitors package initialized
2026-01-28 09:01:37,518 - monitoring_sdk.cloud - DEBUG - monitoring_sdk.cloud package initialized
+ echo === Check completed ===

Evidence that the package's __init__.py was executing existed. But no logs from inside the metabase_check function appeared. No "Starting metabase-check command" log.

To cut to the conclusion, the main() function was never being called.

The Trap of Exit Code 0

Just to be sure, I explicitly checked the exit code.

+ set +e
+ python -m monitoring_sdk.core.cli metabase-check --config config.yaml --output reports
+ EXIT_CODE=$?
+ echo "Exit code: $EXIT_CODE"
Exit code: 0

Indeed 0. Not an error. So what was happening?

I tried --help as well.

+ python -m monitoring_sdk.core.cli --help
+ echo "Help display completed"
Help display completed

No help content output at all. The command was executed (the + symbol appeared), yet the output was completely empty.

This was a state where, even when executing the module with python -m, the entry point wasn't properly configured.

Identifying the Cause: Missing Entry Point

I reviewed the cli.py implementation.

# monitoring_sdk/core/cli.py
import click

@click.group()
def main():
    """CLI entry point for monitoring SDK"""
    pass

@main.command()
@click.option("--config", required=True)
@click.option("--output", default="reports")
def metabase_check(config: str, output: str):
    # ... implementation ...
    pass

It looks fine at first glance. But looking at the end of the file:

# That's it. No if __name__ == "__main__":

This was the cause.

When executing a module with python -m monitoring_sdk.core.cli:

  1. Python imports monitoring_sdk/core/cli.py
  2. Module-level code (import statements, function definitions, decorators) executes
  3. But the main() function isn't called
  4. Exits with exit code 0 doing nothing

Honestly, because it was AI-generated code, I assumed "it must be properly implemented." So I didn't even check for the presence of an entry point in the first place.

Getting stuck here was painful.

Review: Python Entry Points

Since we're here, let me organize thoughts on Python module execution and entry points.

Behavior of python -m

When executing python -m module.path, Python searches for entry points in this order:

  1. If module/path/__main__.py exists, execute it
  2. Otherwise execute module/path.py (but requires if __name__ == "__main__")

What's important is that while it imports the module, it doesn't automatically call main().

Two Methods for Entry Points

Execution Method Required File/Code Purpose
python -m module.path __main__.py Distribute as package
python script.py if __name__ == "__main__" Execute as script

To support both:
It's safer to implement both. In this case, both were missing.

Implementation Example

# monitoring_sdk/core/__main__.py
from monitoring_sdk.core.cli import main

if __name__ == "__main__":
    main()
# monitoring_sdk/core/cli.py (add at the end)
if __name__ == "__main__":
    main()

Now main() will be called through either startup path.

Behavior After Fix

2026-01-28 09:49:07,417 - monitoring_sdk.monitors.metabase_monitor - INFO - Starting card check
2026-01-28 09:49:07,417 - monitoring_sdk.monitors.metabase_monitor - INFO - Card check completed: status=OK
2026-01-28 09:49:07,417 - monitoring_sdk.monitors.metabase_monitor - INFO - Metabase report generation completed: file=reports/summary.md
2026-01-28 09:49:07,418 - monitoring_sdk.cli.commands.metabase_check - INFO - metabase-check command completed: status=OK

It now works as expected.

Lessons Learned: The Sense of "Acceleration and Braking"


For a Comprehensive View of AI Development

To systematically learn not just accelerating AI-driven development, but "where to accelerate and where to brake":

[📦 商品リンク: moshimo-card-HD5Yl]

"AI-Driven Development Guide" explains everything from designing, practicing, to evaluating AI-powered development processes. You'll acquire a mindset that goes beyond mere code generation.


1. Automation Dramatically Reduces Constraint Time

With automation from issue to PR, adding logs to 32 files takes just 3-5 minutes of manual work. While the actual processing takes 2 hours, you can work on other tasks during that time.

Where manual work would require being glued to the task for half a day, the actual constraint time is just 5 minutes. This difference is significant.

2. But Overconfidence in "AI-Generated = Safe" Is Dangerous

The assumption "it must be properly implemented" made me neglect checking basic elements.

The presence or absence of an entry point is something you'd definitely check when handwriting. But when leaving it to AI, you don't even become conscious of it.

3. Silent Failures Are Hard to Notice

No errors, normal exit with code 0. Yet nothing is happening. Without comprehensive logs, such "silent failures" take time to identify the cause.

Thanks to debug logs this time, I could see the situation: "package initialization is running" but "functions aren't being called."

4. Where to Accelerate, Where to Brake

Where to accelerate (AI-driven, speed-focused):

Where to brake (careful verification):

This turns out to be surprisingly crucial.

Summary

AI-driven development is definitely fast. But if you skip reviews with overconfidence that "AI made it, so it's fine," it's surprisingly fragile.

What's important is honing the sense of where to accelerate and where to brake.

This problem was basic, but such basic problems should be carefully verified in the initial testing phase. Not "it should work" but "does it actually work?" That balance is especially important in AI-driven development, as I've come to realize.