dspy 3.1.x: calling dspy.configure(lm=dspy.LM(...)) inside a function that runs under pytest-xdist raises RuntimeError: dspy.settings can only be changed by the thread that initially configured it. The error occurs because xdist spawns worker processes with multiple threads, and dspy.configure() has a thread-ownership check that compares the calling thread to the thread that first called configure(). This is not documented in the dspy API reference or migration guide. Initial assumption was that each test worker would get its own settings instance, but the ownership check spans across the entire process, not per-test.
Use dspy.context(lm=...) (context manager) instead of dspy.configure(lm=...). dspy.context() applies settings at the thread level without modifying global state, so it works safely under pytest-xdist, ThreadPoolExecutor, or any multi-threaded context.
# BROKEN under pytest-xdist / multi-threading:
dspy.configure(lm=dspy.LM(model, api_key=key, max_tokens=16000))
predictor = dspy.Predict(MySignature)
result = predictor(input=data)
# FIXED — thread-safe:
lm = dspy.LM(model, api_key=key, max_tokens=16000)
with dspy.context(lm=lm):
predictor = dspy.Predict(MySignature)
result = predictor(input=data)dspy.context() is documented as: "Context manager for temporary configuration changes at the thread level. Does not affect global configuration. Changes only apply to the current thread." It also propagates to child threads spawned via dspy's ParallelExecutor inside the context block.
This applies to dspy >= 3.0 where the thread-ownership guard was added to dspy.settings.configure().