Skip to content

Errors and timeouts

When DarkRay refuses a request, or can’t be reached, the client raises an exception. Each one carries a sentence saying what went wrong and what to do next.

Every exception is a DarkRayError, with three attributes:

  • message: the plain sentence, such as “Photo 1234 isn’t in the catalog Travel.”
  • code: the bridge’s error code, or None for an error raised on your side of the connection.
  • data: details a program can use (a field’s range, the ids not found), or None.

A call that changes many photos checks every id and value first, and changes all of them or none. So when a call raises, nothing changed:

from darkray_bridge import ControlOff, DarkRay, DarkRayError, NotRunning, OutOfRange
try:
with DarkRay.connect() as app:
ids = [photo.id for photo in app.iter_photos("camera:X-T5 rating:>=3")]
try:
app.set_edits(ids, values={"exposure_ev": 6.0})
except OutOfRange as err:
# Nothing changed. err.data names the field and its range.
app.set_edits(ids, values={"exposure_ev": err.data["max"]})
except (NotRunning, ControlOff) as err:
print(err.message) # says what to do: start DarkRay, or turn MCP on
except DarkRayError as err:
print(f"DarkRay refused: {err.message} ({err.code}, {err.data})")

All are importable from darkray_bridge, except ProtocolError, which lives in darkray_bridge.errors.

Exception Raised when data
NotRunning DarkRay isn’t running: no discovery file, one left by a DarkRay that has quit, or nothing answering on its port
ControlOff DarkRay runs with MCP off
Unauthorized DarkRay refused the connection’s token; connect again
TimedOut DarkRay didn’t answer in time (see below)
Disconnected The connection closed while a call waited: DarkRay quit, or MCP was turned off
ProtocolError A message that wasn’t JSON-RPC, or that DarkRay couldn’t read
UnknownMethod The running DarkRay is older than your darkray_bridge and lacks this MCP tool method
InvalidParams A missing, unknown or mistyped parameter, a search DarkRay can’t read, or an unknown edit field param
NotFound A catalog that isn’t open, or a photo, collection, preset, look or job that doesn’t exist kind, ids
OutOfRange A value outside its field’s range, or not among its choices field with min and max, or choices
TooMany Past a limit: 1,000 photos per call, 500 results per page limit, param
NotAllowed The request can’t run now or here: another catalog is shown, the photo in Edit stayed busy, another merge or import is running, a smart collection, edits saved by a newer DarkRay, photos that aren’t RAWs (merges), an export folder that’s missing, content search not set up reason, such as catalog, busy, smart, newer, not_raw, export, content
Failed The work started and failed, or DarkRay hit an internal error; also raised by Job.wait() when the job fails reason

An error code this client doesn’t know raises a plain DarkRayError with that code.

  • DarkRay.connect(timeout=10) waits up to 10 seconds to connect and say hello.
  • Each call then waits up to 120 seconds for DarkRay’s answer, then raises TimedOut. DarkRay may still finish the work; the client only stops waiting.
  • A call that would change the photo open in Edit while you drag a control, paint a stroke, or the photo loads waits for that to end, and fails with NotAllowed after 20 seconds.
  • Exports, imports and merges don’t block a call: they return a job after at most 20 seconds. job.wait(timeout=…) sets your own limit (see Jobs and previews).

To let every call wait longer, or as long as it takes (None), build the client from a connection yourself:

from darkray_bridge import BridgeConnection, DarkRay
app = DarkRay(BridgeConnection.open("my script"), timeout=600)
Technical details: errors from call() and Job

app.call(name, ...) raises KeyError for a name that isn’t an MCP tool in your copy of darkray_bridge. The error codes behind each exception are listed in The bridge protocol.