Diagnostic: HuggingFace Spaces "Loading..." Forever Bug
Date: 2025-12-10 Status: UNRESOLVED - App stuck on "Loading..." despite backend running Symptom: HF Spaces shows "Running on T4", logs show successful startup, but UI never renders
Observed Behavior
===== Application Startup at 2025-12-10 02:10:47 =====
* Running on local URL: http://0.0.0.0:7860
- Backend Python starts successfully
- Gradio server binds to
0.0.0.0:7860(correct for Docker) - Frontend shows Gradio "Loading..." spinner indefinitely
- No error messages in container logs
Research Findings
1. Known Gradio Issues with Custom JavaScript
Issue #11649: Custom JS via head fails with 404
Source: GitHub Issue #11649
Users report custom JavaScript files failing to load even with allowed_paths configured.
Solution found: Use head_paths parameter instead of head:
with gr.Blocks(head_paths=["custom.html"]) as demo:
Alternative URL format that works: /gradio_api/file=static/custom.js instead of /file/static/custom.js
Issue #10250: JavaScript in head param not executing
Source: GitHub Issue #10250
JavaScript occasionally executes after extended delays (5-10+ minutes) or not at all.
Key insight: gr.HTML() components intentionally do NOT execute JavaScript for security. Only head parameter supports JS execution.
Issue #6426: gr.Blocks head argument not working
Source: GitHub Issue #6426
Two critical bugs:
- Only the FIRST script tag from
headis applied - Script tags are injected AFTER page loads, preventing execution
Fixed in PR #6639 - but may require specific Gradio version.
2. ES Module Script Behavior
Source: ES Modules Explainer
Key facts about <script type="module">:
- Always deferred by default - does NOT block HTML parsing
- No way to make it blocking - even without async/defer
- If import fails, script silently fails but shouldn't block page
Implication: Our NiiVue loader (<script type="module">) should NOT be blocking Gradio's rendering. The issue is elsewhere.
3. HuggingFace Spaces Known Issues
Origin Mismatch Bug (Issue #10893)
Source: HF Forum Thread
Gradio's postMessage calls fail due to origin mismatch between https://huggingface.co and the actual Space URL. This can prevent JavaScript execution entirely.
Docker "Running" But "Loading..." Forever
Source: HF Forum Thread
Common causes:
- Binding to
127.0.0.1instead of0.0.0.0(we already fixed this) - Incorrect port configuration (we use 7860)
- Private Space authentication issues (our Space is public)
Interface Not Showing Despite Running
Source: HF Forum Thread
Assigning Interface to a variable before launching can cause this:
# WRONG
iface = gr.Interface(...).launch()
# RIGHT
gr.Interface(...).launch()
Our code: We use get_demo().launch() which SHOULD be correct.
4. Gradio set_static_paths Requirements
Source: Gradio Docs
- Must be called BEFORE creating Blocks
- Affects ALL Gradio apps in the same interpreter session
- Exposes entire directories to network (security consideration)
Our code: We call gr.set_static_paths() at module level before imports. β
5. Browser Cache Issues
Source: HF Forum Thread
Some "Loading..." issues resolved by clearing browser cache. Works in one browser but not another.
Unlikely cause: This is a fresh deployment, not a cache issue.
Our Current Implementation
JavaScript Loading Flow
app.py(root entry point for HF Spaces Docker):- Calls
gr.set_static_paths(paths=[str(_ASSETS_DIR)])before imports - Imports
get_demo()andget_niivue_loader_path() - Launches with
head_paths=[str(niivue_loader)]
- Calls
ui/app.py(also calls set_static_paths):- Module-level
gr.set_static_paths()before imports - Creates demo with
js_on_load=NIIVUE_ON_LOAD_JSon gr.HTML component
- Module-level
viewer.py:- Generates
niivue-loader.htmlat runtime with absolute path - Content:
<script type="module">import { Niivue } from '/gradio_api/file=...'</script>
- Generates
Files Involved
| File | Purpose | Status |
|---|---|---|
app.py (root) |
HF Spaces entry point | Uses head_paths β |
src/.../ui/app.py |
Main UI module | Uses js_on_load β |
src/.../ui/viewer.py |
NiiVue loader generation | Generates at runtime β |
src/.../ui/components.py |
UI components | Uses NIIVUE_ON_LOAD_JS β |
src/.../ui/assets/niivue.js |
Vendored NiiVue library | 2.9MB, tracked β |
src/.../ui/assets/niivue-loader.html |
Generated loader | gitignored β |
Dockerfile CMD
CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
This runs ui/app.py as __main__, which should execute our launch() with head_paths.
Hypotheses
H1: js_on_load Breaking Gradio Initialization
Theory: The js_on_load parameter on gr.HTML might be executing before Gradio fully initializes, causing a crash.
Evidence: Our code has js_on_load=NIIVUE_ON_LOAD_JS which is a complex async IIFE.
Test: Remove js_on_load parameter and see if app loads.
H2: head_paths Not Being Applied on HF Spaces
Theory: The head_paths parameter might not be reaching the frontend on HF Spaces due to Docker networking or Gradio configuration.
Evidence: Issue #11649 shows head-related parameters have bugs.
Test: Check browser Network tab for niivue.js 404 or missing script.
H3: demo.load() Blocking Initial Render
Theory: The demo.load(initialize_case_selector, ...) call might be blocking the initial UI render.
Evidence: initialize_case_selector() calls list_case_ids() which loads HuggingFace dataset.
Test: Remove demo.load() and see if app loads.
H4: Double set_static_paths Causing Conflict
Theory: Both app.py (root) and ui/app.py call gr.set_static_paths(). This might cause conflicts.
Evidence: Gradio docs say "affects ALL Gradio apps in same interpreter session".
Test: Remove one of the set_static_paths calls.
H5: Module Import Order Issue
Theory: The order of imports and set_static_paths calls might matter on HF Spaces but not locally.
Evidence: We have noqa: E402 comments indicating non-standard import order.
Test: Trace exact import order and when set_static_paths is effective.
H6: Path Resolution Different in Docker
Theory: Path(__file__).resolve() might resolve to different paths in Docker vs local.
Evidence: We use absolute paths for NIIVUE_JS_URL computed at import time.
Test: Log the actual paths being computed in Docker.
Diagnostic Steps to Try
- Minimal Test: Create a branch that removes ALL custom JS and test if basic Gradio loads
- Log Paths: Add logging to show exactly what paths are computed in Docker
- Browser DevTools: Check Network tab and Console for errors (if accessible)
- Gradio Version: Verify we're using a version with all relevant fixes
- HF Spaces Logs: Check full container logs for any Python errors not shown in UI
Related Documentation
- AUDIT_JS_LOADING_ISSUES.md - Previous audit of JavaScript loading issues
- docs/specs/24-bug-hf-spaces-loading-forever.md - Original bug specification
External Resources
- Gradio Custom CSS and JS Guide
- Gradio File Access Guide
- Gradio set_static_paths Docs
- Gradio Issue #11649 - head_paths solution
- Gradio Issue #10250 - head JS not executing
- Gradio Issue #6426 - head argument bugs
- HF Spaces Docker Guide