99 — Known landmines¶
Specific bugs and weird-library-behavior patterns that have bitten agents (or humans) in this repo. Read this BEFORE adding workarounds for unexpected behavior — the answer may already be here.
panns_inference hardcodes ~/panns_data/class_labels_indices.csv¶
The panns_inference PyPI package reads its labels CSV at MODULE-IMPORT
TIME from Path.home() / 'panns_data' / 'class_labels_indices.csv',
with no override hook. If the file's missing, it wgets from a Google
storage URL we don't control. This is a problem for systemd
deployments (HOME varies by user) and for any environment with no
network access at startup.
The fix in this repo:
agents/orpheus-agent-audio-events/src/orpheus_agent_audio_events/model.py
has _stage_panns_labels_and_pin_home() which:
1. Copies the bundled 527-class CSV (from the agent's data/
directory) to $ORPHEUS_DATA_ROOT/panns_data/.
2. Temporarily sets os.environ["HOME"] = $ORPHEUS_DATA_ROOT BEFORE
importing panns_inference.
3. Restores HOME in a finally block.
If you're adding ML deps that read from ~/..., consider whether the
same staging pattern applies.
.gitignore lib/ rule eats frontend files¶
.gitignore has a lib/ rule for Python build artifacts. Without the
negation rules directly under it, this silently catches
services/orpheus_ui/frontend/src/lib/*.ts for new files. git add
fails with "paths ignored by gitignore"; git status shows them in
the "Untracked files (use git status -u)" section that's hidden by
default.
The fix in this repo (already applied):
Always git status after committing. If anything's untracked, ask why
before pushing.
AudioSet machine_ids: NEVER guess¶
The historical curated CSV at
agents/orpheus-agent-audio-events/src/orpheus_agent_audio_events/data/audioset_class_labels_indices.csv
had 40/45 wrong indices because someone built it from a different
AudioSet revision than PANNs uses. Effect: post_process()'s
allowed_class_indices filter dropped EVERY corvid detection silently.
Similar bug in BIRD_LIKE_AUDIOSET_MIDS (Bird Correlation dashboard
config in services/orpheus_ui/backend/src/orpheus_ui/api/diagnostics.py)
— "Caw" was at the Coo mid, "Owl" was at the Bird-flight mid, one mid
didn't exist at all.
The rule: if you need an AudioSet mid, look it up in
agents/orpheus-agent-audio-events/src/orpheus_agent_audio_events/data/panns_class_labels_indices.csv.
This is the canonical 527-class CSV that ships with the model. Never
copy mids from comments, README files, or PANNs documentation —
they've been wrong before.
PANNs frame duration: 0.010s, NOT 0.032s¶
The model's framewise output is at 100 Hz (hop_size=320 @ 32000 Hz).
A previous comment said "~32 ms" which was for a different PANNs
variant. The wrong constant made post_process()'s time bounds off
by 3.2x.
If you find a model parameter you can't trace to a measurement, measure it. Don't trust comments.
OrpheusConfig singleton must be reset between tests¶
OrpheusConfig.get_instance() caches. Tests that read config will see
the previously-loaded config unless they reset it.
The fix: every per-component tests/conftest.py for code that
calls OrpheusConfig.get_instance() has an autouse fixture that
clears the singleton before each test. Copy this pattern when
creating a new test suite:
@pytest.fixture(autouse=True)
def reset_orpheus_config_singleton() -> None:
original_instance = OrpheusConfig._instance
original_dotenv = OrpheusConfig._DOTENV_LOADED
OrpheusConfig._instance = None
OrpheusConfig._DOTENV_LOADED = False
yield
OrpheusConfig._instance = original_instance
OrpheusConfig._DOTENV_LOADED = original_dotenv
Local lint != CI lint unless invoked via make lint-<X>¶
Each component's pyproject.toml enables strict rule sets (S, PT,
N, PTH, RUF, etc). Running venv/bin/ruff check src/ tests/
from outside the component directory does NOT pick up those rules.
make lint-<component> (which cds in and uses the agent's config)
does.
Symptom: local "ruff clean", CI "21 ruff errors."
Fix: Always make lint-<component> before pushing.
See 10-tooling.md.
Ruff strict rule pitfalls¶
When you write tests:
| Rule | What triggers it | Fix |
|---|---|---|
S108 |
Hardcoded /tmp/... paths in test data |
Use tmp_path fixture |
PT018 |
assert a and b (combined) |
Split into two asserts |
PT006 |
@parametrize("a,b", ...) (comma-separated) |
Use tuple syntax: ("a", "b") |
PLC0415 |
Imports inside functions | Add # noqa: PLC0415 if the lazy import is needed (e.g., to skip-if-missing); otherwise move to module top |
PTH123 |
open(path) |
Use path.open() if path is Path, else Path(path).open() |
N806 |
UpperCase variable in function | Lowercase it |
ARG001 / ARG002 |
Unused fn / method arg | Either remove or # noqa: ARG002 — fixture forces X |
RUF003 |
Unicode × in comment |
Use ASCII x |
I001 |
Imports unsorted | make format-<component> will auto-fix |
SQLite ALTER TABLE on non-existent table¶
When ensure_schema_updates() runs on a partially-initialised DB
(some tests init only some tables), an ALTER TABLE entities ADD
COLUMN ... blows up if the entities table doesn't exist yet.
The fix: guard ALTER TABLE with a table-existence check:
cursor.execute("PRAGMA table_info(entities)")
entity_cols = {row[1] for row in cursor.fetchall()}
if entity_cols and "event_signature" not in entity_cols:
cursor.execute("ALTER TABLE entities ADD COLUMN event_signature TEXT")
entity_cols and ... — only attempts the ALTER when the table
actually exists.
Detection / Entity persistence: schema fields fan out everywhere¶
When you add a new field to Detection or Entity, you MUST update:
- The Pydantic model
- The
CREATE TABLEstatement (for fresh DBs) ensure_schema_updates()(for existing DBs — additive ALTER TABLE)DetectionDB.save()/save_entity()(INSERT statements)_row_to_detection()/_row_to_entity()(SELECT side)- Round-trip tests
A missed step is a silent persistence bug. See
31-recipes-schema-changes.md.
Pydantic v2 extra='ignore' is the default¶
If you add a field to a model and old code reads new data, the old
code's Pydantic model has extra='ignore' by default (not 'forbid').
Extra fields are dropped silently. This is intentional for our
forward-compat story but can hide bugs. Test round-trips explicitly.
The PR-number question¶
The PR number for this branch's work is just the next available number in the repo. There is ONE PR per feature branch. Past agents have mistakenly thought "PR #2" implied "PR #1 was for the same work" and spent time looking for it. Look at the PR's branch name; that's the truth.
Cannot update time stamp of directory 'src/<pkg>.egg-info' mid-deploy¶
Symptom: make update-services (or any component make install)
dies partway through an upgrade with:
Cause: every component's make install does an editable install
(-e .[dev] in its requirements.txt), and setuptools regenerates
src/<pkg>.egg-info/ in the git working tree each time. A past sudo'd
run (from before the "don't run make install as root" guards) can
leave that directory root-owned; the later non-sudo reinstall can't
touch it and setuptools fails with the cryptic timestamp error above.
The fix in this repo (automatic): make/common_python.mk hooks a
preclean-egg-info prerequisite onto every component's install
target. It removes *.egg-info dirs immediately before the editable
install (setuptools regenerates them, so this is always safe), and if
one can't be removed (foreign-owned, no sudo), it fails fast with the
exact one-liner instead of the timestamp error.
Manual one-liner (from the component directory):
pip "WARNING: Ignoring invalid distribution -..." is debris, not damage¶
Symptom: installs/updates print lines like:
Cause: an interrupted or killed pip run. While upgrading a package,
pip renames the old version's folder to ~name inside site-packages
and deletes it at the end; if the process dies in between, the ~name
folder is stranded. pip then warns about it on every later invocation
(the leading ~ is displayed as - in the warning). Folders starting
with - or ~ in site-packages are never legitimate.
Harmless to runtime — the real, valid distribution is still installed and imports fine. But the debris hides real warnings, so delete it:
# from the component dir; prefix with sudo for the /opt/orpheus venvs
find venv/lib/python*/site-packages -maxdepth 1 -name '~*' -exec rm -rf {} +
If a warning names a package you actually need and imports break,
reinstall it afterwards (make install / make reinstall).
When you add to this file¶
If you hit a weird-library-behavior problem and figure it out, write it down here. The next agent will save a session's worth of effort.