MemPalace/mempalace · warning · DaemonError
timed out waiting for job {job_id}
Error message
timed out waiting for job {job_id} What it means
DaemonError raised by the client's job-wait loop (mempalace/daemon.py:1217): the job had not reached a terminal state before the caller's timeout deadline (checked with time.monotonic after each 0.2s poll). The job itself is not cancelled — it continues in the daemon queue; only the local wait aborts.
Source
Thrown at mempalace/daemon.py:1217
self,
job_id: str,
*,
timeout: float = DEFAULT_WAIT_TIMEOUT,
stop_on_lock_deferral: bool = False,
) -> dict[str, Any]:
deadline = time.monotonic() + timeout
while True:
job = self.get_job(job_id)
if job["state"] in TERMINAL_STATES:
return job
# A job parked behind the palace lock never becomes terminal on its
# own, so an interactive caller must be able to stop here instead of
# waiting out the holder (#2014). Background callers keep the old
# behaviour and simply wait.
if stop_on_lock_deferral and job_deferred_by_lock(job):
return job
if time.monotonic() >= deadline:
raise DaemonError(f"timed out waiting for job {job_id}")
time.sleep(0.2)
def shutdown(self) -> dict[str, Any]:
return self.request("POST", "/shutdown", {})
def get_client_if_running(palace_path: str, *, health_timeout: float = 5.0) -> DaemonClient | None:
# health_timeout bounds the liveness probe. Hook callers (subject to the
# ~500ms hook budget) pass a short value via HOOK_PROBE_TIMEOUT so a wedged
# daemon — endpoint present, HTTP server not answering — can't stall the
# hook for the default 5s before it falls back to the direct path.
try:
client = DaemonClient(palace_path)
client.health(timeout=health_timeout)
return client
except DaemonError:
return None
View on GitHub (pinned to 06cb6987f0)
Solutions
- Increase the timeout for genuinely long jobs (indexing, embedding over large chunks).
- If the job may be parked behind the palace lock, use the interactive path with stop_on_lock_deferral=True to return the deferred job instead of timing out.
- Check client.get_job(job_id) / counts() to see queue depth and whether the job is running, queued, or deferred; free the lock holder if deferred.
- Treat the timeout as 'not done yet', not failure: poll again later or let the daemon finish in the background.
Example fix
# before
job = client.wait_for_job(job_id, timeout=5)
# after
job = client.wait_for_job(job_id, timeout=120, stop_on_lock_deferral=True)
if not is_terminal(job):
# resume later; job still queued in daemon Defensive patterns
Strategy: retry
Try / catch
from mempalace.daemon import DaemonError
try:
job = client.wait_for_job(job_id, timeout=120, stop_on_lock_deferral=True)
except DaemonError as exc:
if "timed out" in str(exc):
job = client.get_job(job_id) # still queued/running; check later, don't fail
else:
raise Prevention
- Size timeouts to the job kind (indexing/embedding >> save).
- Use stop_on_lock_deferral for interactive callers so lock parking returns early.
- Treat timeout as 'pending', poll again or inspect queue counts before resubmitting.
When it happens
Trigger: Waiting on a long-running indexing/embedding job with a short timeout; the queue is backed up behind higher-priority jobs; the job is parked waiting on the palace lock held by another writer (interactive callers can pass stop_on_lock_deferral to return early instead); a wedged worker.
Common situations: Hook budgets (~500ms) forcing tiny timeouts on genuinely slow jobs; bulk ingest saturating the single worker; another process holding the palace lock so the job defers indefinitely (see #2014 referenced in the source); default timeouts too small for cold-start model loads.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- daemon is not running
- daemon is not running; job {args.job_id} is {job['state']}
- unknown job id: {job_id}
- writable daemon startup refused: another writer owns local b
- daemon did not become ready: {last_error}
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/5e69b0b9f4dbc301.
Report an issue: GitHub.