Skip to content

plain shell: piping a script via stdin skips runtime.setup() (opaque PackageRegistryNotReady) #66

Description

@davegaeddert

Summary

plain shell initializes the runtime by setting PYTHONSTARTUP to a startup file that calls plain.runtime.setup(). Python only honors PYTHONSTARTUP in interactive sessions, so when stdin is a pipe (heredoc, redirected file, etc.) the startup file is silently skipped, the package registry never loads, and any from app.<pkg>.models import ... raises PackageRegistryNotReady.

The failure is opaque — there's no hint that the issue is "you ran this non-interactively." plain shell -c \"...\" and plain run <script> work because they explicitly prepend import plain.runtime; plain.runtime.setup() before the user code.

Repro

In any Plain project with at least one local package:

```sh
uv run plain shell <<'PY'
from app.users.models import User
print(User.query.count())
PY
```

Yields:

```
Traceback (most recent call last):
File "", line 1, in
File ".../plain/postgres/registry.py", line 214, in register_model
model_class.model_options.package_label,
...
plain.exceptions.PackageRegistryNotReady: Packages aren't loaded yet.
```

Workarounds: `plain shell -c "..."` or `plain run script.py`.

Suggested fix

Either:

  1. In `plain/cli/shell.py`, detect non-TTY stdin (`sys.stdin.isatty() is False`) and route to the same `-c`-style path that prepends `plain.runtime.setup()` (e.g. read stdin and `exec` it after setup, like `plain run` does for files).
  2. Or, at minimum, emit a clearer error when `PackageRegistryNotReady` is raised from a CLI entry — pointing users at `plain run` / `plain shell -c`.

(1) is the friendlier behavior and matches what most users probably expect from `plain shell <<EOF ... EOF`.

Environment

  • plain 0.139.0
  • Python 3.13.3
  • macOS 25.4.0

Surfaced while smoke-testing new models in a fresh `plain-start` app — piped a heredoc into `plain shell`, hit `PackageRegistryNotReady`, took a minute to realize `PYTHONSTARTUP` was the culprit. Logging it as a small UX gap, not a bug.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions