Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/desktop/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1926,6 +1926,23 @@ function resolveGitBinary() {

if (localAppData) {
candidates.push(path.join(localAppData, 'Programs', 'Git', 'cmd', 'git.exe'))

// UGit ships a portable Git under %LOCALAPPDATA%\UGit\app-*\resources\app\git\cmd\git.exe.
// The versioned app-* subdirectory changes with each UGit update, so enumerate
// all app-* directories dynamically instead of hard-coding a version (#61494).
const ugitDir = path.join(localAppData, 'UGit')
try {
const entries = fs.readdirSync(ugitDir, { withFileTypes: true })
for (const entry of entries) {
if (entry.isDirectory() && entry.name.startsWith('app-')) {
candidates.push(
path.join(ugitDir, entry.name, 'resources', 'app', 'git', 'cmd', 'git.exe')
)
}
}
} catch {
// UGit not installed or inaccessible — skip gracefully
}
}

_gitBinaryCache = candidates.find(fileExists) || findOnPath('git') || 'git'
Expand Down
26 changes: 25 additions & 1 deletion tools/cronjob_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,15 @@ def _execute_job_now(job: Dict[str, Any]) -> Dict[str, Any]:
failure delivery, ``[SILENT]`` handling, and live-adapter delivery stay
identical across paths and can't drift.

When a live gateway is running, this function retrieves the gateway's
adapters and event loop and passes them to ``run_one_job`` so that job
delivery goes through the live-adapter path (``safe_schedule_threadsafe``
on the gateway loop) rather than creating a new event loop via
``asyncio.run()``. The standalone path would send adapter HTTP calls
from a different loop than the adapter's internal aiohttp session was
created on, causing "Timeout context manager should be used inside a task"
failures for platforms such as Matrix (#61495).

Returns {"claimed": bool, "success": bool, "error": str|None}.
"""
job_id = job["id"]
Expand All @@ -636,9 +645,24 @@ def _execute_job_now(job: Dict[str, Any]) -> Dict[str, Any]:
reason = "Job is already being fired by the scheduler; not run again."
return {"claimed": False, "success": False, "error": reason}

# Resolve the gateway's adapters and event loop so delivery uses the
# live-adapter path (safe_schedule_threadsafe on the gateway loop).
# Without these, _deliver_result falls back to asyncio.run() in a new
# event loop, causing cross-loop adapter usage failures (#61495).
_adapters = None
_loop = None
try:
from gateway.run import _gateway_runner_ref
_runner = _gateway_runner_ref()
if _runner is not None:
_adapters = getattr(_runner, "adapters", None)
_loop = getattr(_runner, "_gateway_loop", None)
except Exception:
pass

# run_one_job records last_run_at/last_status via mark_job_run (which
# also clears the fire claim) and returns True iff it processed the job.
processed = run_one_job(job)
processed = run_one_job(job, adapters=_adapters, loop=_loop)
refreshed = get_job(job_id) or {}
ok = refreshed.get("last_status") == "ok"
return {
Expand Down
21 changes: 18 additions & 3 deletions tools/send_message_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1695,9 +1695,24 @@ async def _send_matrix_via_adapter(pconfig, chat_id, message, media_files=None,
# disconnect it. Correctness here depends on this branch returning
# before the ephemeral ``adapter`` is constructed below, so the
# ephemeral ``finally`` disconnect never touches the live session.
return await _matrix_send_core(
live_adapter, chat_id, message, media_files, metadata
)
try:
return await _matrix_send_core(
live_adapter, chat_id, message, media_files, metadata
)
except RuntimeError as _matrix_loop_err:
# When the cron standalone path (asyncio.run() in a new event
# loop) uses the live adapter, the adapter's internal aiohttp
# session was created on the gateway's loop and cannot be used
# from a different loop. Fall through to the ephemeral adapter
# rather than failing delivery (#61495). The primary fix is in
# _execute_job_now (which passes the gateway loop so delivery
# uses safe_schedule_threadsafe), but this cross-loop guard
# catches any remaining path.
logger.warning(
"Matrix: live adapter unusable from this event loop "
"(%s: %s); falling back to ephemeral adapter",
type(_matrix_loop_err).__name__, _matrix_loop_err,
)

# --- Fallback: ephemeral adapter (standalone / cron context) ---
try:
Expand Down
Loading