You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
get_interface / get_router raise TypeError when the interface or router has no status yet #4028
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.
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
Create a plugin with an interface and a microservice that polls get_interface('<INTERFACE_NAME>') on startup.
Install the plugin.
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.
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.
# openc3/python/openc3/api/interface_api.pyreturninterface| (InterfaceStatusModel.get(name=interface_name, scope=scope) or {})
# openc3/python/openc3/api/router_api.pyreturnrouter| (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/...):
Create the InterfaceModel/RouterModel but don't create a status record (or delete it).
Check that get_interface/get_router returns the config without raising, and that state is either missing or your chosen default.
Activity
crawlchat commentedon Oct 9, 2026
You're right. In both languages,
get_interface()andget_router()merge the status record without checking that it exists. If the interface or router microservice hasn't written its status yet,*StatusModel.get()returnsnil/Noneand the merge fails.Where it happens (current
main):interface_api.rb:52:interface.merge(InterfaceStatusModel.get(name: interface_name, scope: scope))interface_api.py:51:return interface | InterfaceStatusModel.get(name=interface_name, scope=scope)router_api.rb:51:router.merge(RouterStatusModel.get(name: router_name, scope: scope))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:
With an empty hash,
stateis simply missing (nil/None). Code that polls forstate == '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/...):InterfaceModel/RouterModelbut don't create a status record (or delete it).get_interface/get_routerreturns the config without raising, and thatstateis either missing or your chosen default.