Skip to content

[HOTFIX] Use importlib.util.find_spec for pluggable worker discovery#1918

Open
chandrasekharan-zipstack wants to merge 3 commits intov0.163.2-hotfixfrom
fix/pluggable-worker-find-spec-v0.163.2
Open

[HOTFIX] Use importlib.util.find_spec for pluggable worker discovery#1918
chandrasekharan-zipstack wants to merge 3 commits intov0.163.2-hotfixfrom
fix/pluggable-worker-find-spec-v0.163.2

Conversation

@chandrasekharan-zipstack
Copy link
Copy Markdown
Contributor

@chandrasekharan-zipstack chandrasekharan-zipstack commented Apr 17, 2026

What

Replaces the filesystem worker.py existence check in WorkerBuilder._verify_pluggable_worker_exists() with importlib.util.find_spec().

Why

The pre-import check at workers/shared/infrastructure/config/builder.py:338 hardcoded worker.py as the file to look for. This breaks any deployment where the pluggable worker has been compiled to an extension module (e.g. Nuitka or Cython produces a worker.cpython-312-x86_64-linux-gnu.so that replaces worker.py) — the module is perfectly importable, but the hard .py existence check rejects the worker before importlib.import_module() ever runs.

Observed error pattern (paraphrased):

ERROR: Pluggable worker file not found: /app/pluggable_worker/<name>/worker.py
ImportError: Pluggable worker '<name>' not found.
Expected module: pluggable_worker.<name>.worker

This is not specific to any one obfuscation tool — the check would also reject plugins distributed as Cython .so, bytecode-only .pyc, namespace packages, or any future loader that doesn't materialize a .py file on disk.

How

Use Python's standard "is this module importable?" primitive instead of filesystem introspection:

module_path = f"pluggable_worker.{worker_type.value}.worker"

if importlib.util.find_spec(module_path) is None:
    logger.error(f"Pluggable worker module not importable: {module_path}")
    return False

importlib.import_module(module_path)

find_spec() consults every registered finder (source .py, compiled .so, bytecode .pyc, namespace packages, zipimports) and returns None cleanly if nothing can produce the module. The subsequent import_module() still catches findable-but-broken modules (e.g. ImportError inside the loaded module).

The only semantic change: when a parent package exists but an intermediate subpackage is missing (pluggable_worker/ exists but pluggable_worker/<name>/ does not), find_spec raises ModuleNotFoundError. That's a subclass of ImportError and is already handled by the existing except ImportError block, so the function correctly returns False just like before.

Can this PR break any existing features?

No. Behavior preserved across all deployment scenarios, verified via a synthetic fixture that compiles a test worker with Nuitka and runs _verify_pluggable_worker_exists() against it:

Scenario .py present .so present Before After
No subpackage (pluggable workers disabled) no no False (file not found) False (ImportError)
Source available yes no True True
Compiled extension module no yes False ← bug True ← fixed

Relevant Docs

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a6536540-703e-4261-95e3-0bc96ef664e1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pluggable-worker-find-spec-v0.163.2

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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 17, 2026

Greptile Summary

This hotfix replaces the hardcoded worker.py filesystem existence check in _verify_pluggable_worker_exists() with importlib.util.find_spec(), allowing pluggable workers compiled to .so (Nuitka/Cython) or other non-.py forms to be correctly discovered. A secondary fix in worker.py corrects the core-worker directory derivation for api-deployment, whose on-disk path uses hyphens — the old value.replace("-", "_") was a no-op on already-underscored enum values and would have missed the actual directory.

Confidence Score: 5/5

Safe to merge — both changes are targeted correctness fixes with no regressions on the unaffected paths.

The builder.py change is correct and the previously flagged ValueError is now handled. The worker.py fix properly resolves the api-deployment directory mismatch. No new P0/P1 issues found; all remaining notes from prior review threads have been addressed.

No files require special attention.

Important Files Changed

Filename Overview
workers/shared/infrastructure/config/builder.py Replaces filesystem .py check with importlib.util.find_spec(); ValueError now caught alongside ImportError; logic and exception handling are correct.
workers/worker.py Core-worker directory derivation now uses to_import_path().rsplit() instead of a blind replace, correctly resolving api-deployment's hyphenated on-disk path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["_verify_pluggable_worker_exists(worker_type)"] --> B{is_pluggable?}
    B -- No --> C[return True]
    B -- Yes --> D["module_path = 'pluggable_worker.{worker_type}.worker'"]
    D --> E["importlib.util.find_spec(module_path)"]
    E -- "returns None" --> F["logger.error\nreturn False"]
    E -- "raises ValueError" --> G["except ImportError | ValueError\nreturn False"]
    E -- "spec found" --> H["importlib.import_module(module_path)"]
    H -- "ImportError / ValueError" --> G
    H -- "OSError / AttributeError" --> I["except OSError | AttributeError\nreturn False"]
    H -- success --> J["logger.debug\nreturn True"]
Loading

Reviews (4): Last reviewed commit: "[FIX] Resolve api-deployment worker dire..." | Re-trigger Greptile

Comment thread workers/shared/infrastructure/config/builder.py
Comment thread workers/shared/infrastructure/config/builder.py
_verify_pluggable_worker_exists() previously checked for the literal file
`pluggable_worker/<name>/worker.py` on disk, which breaks when the plugin
has been compiled to a .so (Nuitka, Cython, or any C extension) — the
module is perfectly importable but the pre-check rejects it because only
the .py extension is considered.

Replace the filesystem check with importlib.util.find_spec(), which is
Python's standard way to ask "is this module resolvable by the import
system?". It honors every registered finder — source .py, compiled .so,
bytecode .pyc, namespace packages, zipimports — so the function now
matches what its docstring claims: verifying the module can be loaded,
not that a specific file extension is present.

Behavior is preserved for existing deployments:
- Images with no `pluggable_worker/<name>/` subpackage → find_spec
  raises ModuleNotFoundError (ImportError subclass) → returns False.
- Images with source .py → find_spec resolves the .py → returns True.
- Images with compiled .so → find_spec resolves the .so → returns True.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile-flagged edge case: importlib.util.find_spec() can raise
ValueError (not just ImportError) when sys.modules has a partially
initialised module entry with __spec__ = None from a prior failed import.
Broaden the except to catch both.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@muhammad-ali-e muhammad-ali-e left a comment

Choose a reason for hiding this comment

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

added a comment

# module representation (source .py, Nuitka/Cython .so, .pyc,
# namespace packages, zipimports) — unlike a filesystem check
# for a specific file extension, which breaks for compiled plugins.
module_path = f"pluggable_worker.{worker_type.value}.worker"
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.

Hope this not break running worker locally insated of docker

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Tested locally — doesn't break local runs. find_spec() is actually a superset of the previous filesystem check.

Local runs go through workers/run-worker.sh, which sets export PYTHONPATH="$WORKERS_DIR:${PYTHONPATH:-}" (line 702) before launching celery. So import pluggable_worker.<name>.worker resolves the same way the launcher itself uses the dotted module name at line 444 (celery_app_module="pluggable_worker.${worker_type}.worker").

Coverage comparison vs old code:

Scenario Old (Path(...)/worker.py) New (find_spec)
Local .py via run-worker.sh ✅ (PYTHONPATH set by script)
Docker with uncompiled plugin
Docker with compiled .so (Cython/Nuitka) ❌ (checks .py)
OSS with no pluggable plugins n/a — is_pluggable() returns False, early return same
Bare python worker.py from repo root, no PYTHONPATH ❌ (and would fail at actual import too)

The only way someone could get a false negative from the new code but not the old is if they ran a pluggable worker outside run-worker.sh without any PYTHONPATH setup — in that case the old check returned True but celery would have crashed on the real import a few lines later. So net change: we fail earlier with a cleaner log instead of getting a stack trace.

Also confirmed on snapshot.180420262 locally: pluggable workers (agentic_callback + bulk_download worker.so) register and run fine — previously they crash-looped because Path(...)/worker.py returned False for compiled modules.

worker.py:452 did worker_type.value.replace("-", "_") to derive the
on-disk dir name. All WorkerType enum values already use underscores,
so the replace was a no-op; for API_DEPLOYMENT whose dir is
"api-deployment" (hyphen), it resolved to "api_deployment" and the
os.path.exists() check failed. Boot then logged a spurious
"❌ Worker directory not found: /app/api_deployment" at ERROR level.

The task registration path (builder + celery autodiscover via
to_import_path) is unaffected, so this was purely log noise — but
noise at ERROR level that masks real failures in log scans.

Fix: derive the directory from the authoritative to_import_path()
which already handles the hyphen case (api_deployment -> api-deployment).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

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