# Troubleshooting on an air-gapped host

Symptoms below were either hit while building and verifying this bundle, or are
the failure modes an isolated network causes. Each entry names the cause.

## Launch hangs for 10-30 seconds, then works

Gradio's analytics POST and huggingface_hub's metadata lookups have no route
out. They do not fail fast; they wait for the TCP timeout. Every launch pays it.

    source /path/to/gradio-template/gradio-offline.env

That sets `GRADIO_ANALYTICS_ENABLED=False` and `HF_HUB_OFFLINE=1`, and startup
becomes immediate.

## `ValueError: When localhost is not accessible, a shareable link must be created`

After binding the port, gradio makes an HTTP request to *itself* to confirm the
app is reachable. The message is misleading: `share=True` cannot help offline.
Real causes, in order of likelihood:

1. **A proxy variable is set.** `http_proxy` / `https_proxy` in the environment
   sends gradio's self-check to a proxy that cannot be reached. Exempt
   loopback:

       export no_proxy="127.0.0.1,localhost,::1"
       export NO_PROXY="$no_proxy"

2. **The `/info` endpoint is returning 500.** The self-check fails because the
   app itself is erroring. Look further up the log for the actual traceback --
   see the pydantic entry below.

3. **Loopback is down** in a container or network namespace:
   `ip link set lo up`.

## `ImportError: cannot import name 'HfFolder' from 'huggingface_hub'`

gradio 4.44.1 uses `huggingface_hub.HfFolder`, which was deleted in
huggingface-hub 1.0. `import gradio` fails outright. The Python 3.9 lock pins
`huggingface-hub<1.0` for this reason. If you rebuild the wheel set, keep
`requirements/constraints-py39.txt` in the `pip download` command.

## `TypeError: argument of type 'bool' is not iterable`

Raised from `gradio_client/utils.py` in `get_type`, usually while serving
`/info`, and typically triggered by `gr.File` or `gr.JSON` in the app.

pydantic 2.11 started emitting `"additionalProperties": true` -- a bool where
gradio_client 1.3.0's schema walker expects a dict. The knock-on effects are a
broken `/info` endpoint, a `gradio_client` that cannot connect, and the
"localhost is not accessible" error above. gradio 5+ fixed the walker; on the
4.x branch the fix is to hold pydantic down, which the Python 3.9 lock does:

    pydantic<2.11

## `TypeError: Blocks.launch() got an unexpected keyword argument 'show_api'`

`launch()` rejects unknown keywords, and gradio 6 removed several gradio 4
options, `show_api` among them. Pass only arguments both majors accept, or gate
them on `gradio.__version__` the way `templates/_common.py` does.

## `TypeError: Interface.__init__() got an unexpected keyword argument 'allow_flagging'`

Renamed to `flagging_mode` in gradio 5. The reverse happens on gradio 4 if you
pass `flagging_mode`. Passing the wrong one raises even when the value is
`None`, so select the name at runtime -- `no_flagging()` in
`templates/_common.py` does this.

Leaving flagging enabled is itself a small annoyance: gradio writes flagged
samples into a `flagged/` directory in the process's working directory.

## `ModuleNotFoundError: No module named 'matplotlib'`

matplotlib is a hard dependency of gradio 4.x but not of gradio 5/6, which ship
native plot components instead. `gr.Plot` still renders a matplotlib `Figure`,
so this bundle installs matplotlib explicitly in the Python 3.12 set. If you
rebuild, keep the extra line in `requirements/gradio-py312.in`.

## Plot renders blank, or `UserWarning: Matplotlib is building the font cache`

A server has no display, and a service account may have no writable `HOME`.

    export MPLBACKEND=Agg
    export MPLCONFIGDIR=/var/tmp/matplotlib-$(id -u)

Both are set by `gradio-offline.env`. The font-cache warning on first run is
harmless and happens once per `MPLCONFIGDIR`.

## `gradio_client`: "Cannot find a function with api_name: /predict"

Endpoint names are not stable across majors. gradio 4 names a single
`gr.Interface` function `/predict`; gradio 5+ derives it from the function name
instead. Read the names from the schema rather than hardcoding them, as
`templates/08_api_client.py` does:

    named = list(client.view_api(return_format="dict")["named_endpoints"])

## Uploads fail with a permission error

Uploads go to `GRADIO_TEMP_DIR` (default `/tmp/gradio-<uid>`). Under SELinux,
a confined service needs a context it may write. Prefer a dedicated directory:

    mkdir -p /var/lib/gradio/tmp
    chown gradio:gradio /var/lib/gradio/tmp
    semanage fcontext -a -t var_lib_t "/var/lib/gradio(/.*)?"
    restorecon -Rv /var/lib/gradio
    export GRADIO_TEMP_DIR=/var/lib/gradio/tmp

## Reachable from the host itself but not from other machines

The default bind is loopback. To publish it, bind all interfaces and open the
port:

    export GRADIO_SERVER_NAME=0.0.0.0
    firewall-cmd --add-port=7860/tcp --permanent && firewall-cmd --reload

If SELinux blocks the bind on a non-standard port:

    semanage port -a -t http_port_t -p tcp 7860

## `share=True` never works

It downloads a tunnel binary from gradio's CDN and registers with a public relay
service. Neither is reachable, and both would defeat the point of an air-gapped
deployment. `gradio-offline.env` sets `GRADIO_SHARE=False`.

## Verifying a suspected regression

`scripts/verify-offline.sh` installs each set into a throwaway venv inside an
`unshare -n` namespace and starts every template. If it passes, the bundle is
intact and the problem is in your application code or the host configuration.
