Skip to content

get_interface / get_router raise TypeError when the interface or router has no status yet #4028

Closed

Description

@calmonroe

Describe the bug

get_interface() and get_router() merge the stored config (InterfaceModel / RouterModel) with the runtime status (InterfaceStatusModel / RouterStatusModel). The status record is only written once the interface or router microservice has started. Until then, *StatusModel.get() returns None/nil, and the merge raises an error instead of returning a usable result.

This shows up most often right after a plugin is installed. The operator starts the plugin's custom microservices alongside the interface microservices, so any microservice that calls get_interface() at startup, for example to wait until an interface is connected, can hit the gap before the interface has written its first status.

Python (openc3/python/openc3/api/interface_api.py:51):
return interface | InterfaceStatusModel.get(name=interface_name, scope=scope)

TypeError: unsupported operand type(s) for |: 'dict' and 'NoneType'

Ruby (openc3/lib/openc3/api/interface_api.rb:52):
interface.merge(InterfaceStatusModel.get(name: interface_name, scope: scope))

TypeError: no implicit conversion of nil into Hash

get_router has the same pattern in openc3/python/openc3/api/router_api.py:51 and openc3/lib/openc3/api/router_api.rb:51.

To Reproduce

  1. Create a plugin with an interface and a microservice that polls get_interface('<INTERFACE_NAME>') on startup.
  2. Install the plugin.
  3. If the microservice calls get_interface before the interface microservice has written its status, the call raises the error above.

You can reproduce it more reliably by deleting the interface's status entry, or by calling get_interface on an installed interface whose microservice isn't running.

Expected behavior

get_interface and get_router should return the config even when there's no status record. The missing status could be treated as an empty hash, or as default status values such as state: 'DISCONNECTED'. That way, code that polls for state == 'CONNECTED' will keep waiting instead of crashing.

Screenshots

get_interface_error (1).png

OS

No response

OpenC3 COSMOS Version

7.3.0

Browser

No response

Activity

  1. added theissue type on Oct 9, 2026
  2. crawlchat commented on Oct 9, 2026

    @crawlchat

    You're right. In both languages, get_interface() and get_router() merge the status record without checking that it exists. If the interface or router microservice hasn't written its status yet, *StatusModel.get() returns nil/None and the merge fails.

    Where it happens (current main):

    • Ruby interface_api.rb:52: interface.merge(InterfaceStatusModel.get(name: interface_name, scope: scope))
    • Python interface_api.py:51: return interface | InterfaceStatusModel.get(name=interface_name, scope=scope)
    • Ruby router_api.rb:51: router.merge(RouterStatusModel.get(name: router_name, scope: scope))
    • Python router_api.py:51: return router | RouterStatusModel.get(name=router_name, scope=scope)
    Suggested fix

    Use an empty hash when there's no status record:

    # openc3/lib/openc3/api/interface_api.rb
    interface.merge(InterfaceStatusModel.get(name: interface_name, scope: scope) || {})
    
    # openc3/lib/openc3/api/router_api.rb
    router.merge(RouterStatusModel.get(name: router_name, scope: scope) || {})
    # openc3/python/openc3/api/interface_api.py
    return interface | (InterfaceStatusModel.get(name=interface_name, scope=scope) or {})
    
    # openc3/python/openc3/api/router_api.py
    return router | (RouterStatusModel.get(name=router_name, scope=scope) or {})

    With an empty hash, state is simply missing (nil/None). Code that polls for state == 'CONNECTED' keeps waiting instead of crashing. If you'd rather have an explicit default, merge {'state' => 'DISCONNECTED'} (Ruby) or {"state": "DISCONNECTED"} (Python) first. That makes the output look more like a real status record, but it reports a state the microservice never actually wrote.

    Tests to add

    For both Ruby (spec/api/interface_api_spec.rb, router_api_spec.rb) and Python (test/api/...):

    1. Create the InterfaceModel/RouterModel but don't create a status record (or delete it).
    2. Check that get_interface/get_router returns the config without raising, and that state is either missing or your chosen default.
  3. self-assigned this
    on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions