personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(service): macOS service_is_running parses launchd state, not just load status

launchctl print gui/<uid>/<label> exits 0 whenever the job is loaded, regardless of whether it is running. The old code returned result.returncode == 0, so a loaded-but-stopped launchd job was reported as running.

This was masked by the previous always-respawn KeepAlive: True plist, where a loaded job was effectively always running. Commit 4a187e1d changed the plist to KeepAlive: {SuccessfulExit: false}, so sol service stop now leaves the supervisor stopped and exposed sol service status reporting the wrong state.

Fix macOS service_is_running by parsing the line-anchored top-level launchd state line from stdout: \tstate = running. The check is anchored to avoid matching nested output such as \t\tjob state = running.

Add realistic launchctl stdout fixtures for running, not-loaded, and loaded-but-stopped states, including the loaded-but-stopped regression case.

+77 -4
+74 -3
tests/test_service.py
··· 247 247 monkeypatch.setattr(service, "service_is_installed", lambda: True) 248 248 monkeypatch.setattr(service, "_platform", lambda: "darwin") 249 249 monkeypatch.setattr(service.os, "getuid", lambda: 501) 250 - run_mock = MagicMock(return_value=MagicMock(returncode=0)) 250 + launchctl_stdout = """gui/501/org.solpbc.solstone = { 251 + \tactive count = 1 252 + \tpath = /Users/jer/Library/LaunchAgents/org.solpbc.solstone.plist 253 + \ttype = LaunchAgent 254 + \tstate = running 255 + \tprogram = /Users/jer/.local/bin/sol 256 + \tpid = 12345 257 + \tdomain = gui/501 258 + \tasid = 100012 259 + \tlast exit code = 0 260 + \trun interval = 0 261 + \tactive transactions = 0 262 + \tdefault environment = { 263 + \t\tPATH => /usr/bin:/bin 264 + \t} 265 + \tenvironment = { 266 + \t\tHOME => /Users/jer 267 + \t} 268 + \tdomain = gui/501 269 + \tminimum runtime = 10 270 + \texit timeout = 5 271 + \tendpoints = { 272 + \t} 273 + \tevent triggers = { 274 + \t} 275 + \tpid local dispatch queue = { 276 + \t\tjob state = running 277 + \t} 278 + } 279 + """ 280 + run_mock = MagicMock( 281 + return_value=MagicMock(returncode=0, stdout=launchctl_stdout) 282 + ) 251 283 monkeypatch.setattr(service.subprocess, "run", run_mock) 252 284 assert service.service_is_running() is True 253 285 254 - def test_service_is_running_false_darwin(self, monkeypatch): 286 + def test_service_is_running_false_when_not_loaded_darwin(self, monkeypatch): 287 + monkeypatch.setattr(service, "service_is_installed", lambda: True) 288 + monkeypatch.setattr(service, "_platform", lambda: "darwin") 289 + monkeypatch.setattr(service.os, "getuid", lambda: 501) 290 + run_mock = MagicMock(return_value=MagicMock(returncode=1, stdout="")) 291 + monkeypatch.setattr(service.subprocess, "run", run_mock) 292 + assert service.service_is_running() is False 293 + 294 + def test_service_is_running_false_when_loaded_but_stopped_darwin(self, monkeypatch): 255 295 monkeypatch.setattr(service, "service_is_installed", lambda: True) 256 296 monkeypatch.setattr(service, "_platform", lambda: "darwin") 257 297 monkeypatch.setattr(service.os, "getuid", lambda: 501) 258 - run_mock = MagicMock(return_value=MagicMock(returncode=1)) 298 + launchctl_stdout = """gui/501/org.solpbc.solstone = { 299 + \tactive count = 0 300 + \tpath = /Users/jer/Library/LaunchAgents/org.solpbc.solstone.plist 301 + \ttype = LaunchAgent 302 + \tstate = not running 303 + \tprogram = /Users/jer/.local/bin/sol 304 + \tdomain = gui/501 305 + \tasid = 100012 306 + \trun interval = 0 307 + \tactive transactions = 0 308 + \tdefault environment = { 309 + \t\tPATH => /usr/bin:/bin 310 + \t} 311 + \tenvironment = { 312 + \t\tHOME => /Users/jer 313 + \t} 314 + \tdomain = gui/501 315 + \tminimum runtime = 10 316 + \texit timeout = 5 317 + \tendpoints = { 318 + \t} 319 + \tevent triggers = { 320 + \t} 321 + \tpid local dispatch queue = { 322 + \t\tjob state = exited 323 + \t} 324 + \tlast exit code = 0 325 + } 326 + """ 327 + run_mock = MagicMock( 328 + return_value=MagicMock(returncode=0, stdout=launchctl_stdout) 329 + ) 259 330 monkeypatch.setattr(service.subprocess, "run", run_mock) 260 331 assert service.service_is_running() is False 261 332
+3 -1
think/service.py
··· 74 74 capture_output=True, 75 75 text=True, 76 76 ) 77 - return result.returncode == 0 77 + if result.returncode != 0: 78 + return False 79 + return "\n\tstate = running\n" in result.stdout 78 80 result = subprocess.run( 79 81 ["systemctl", "--user", "is-active", SYSTEMD_UNIT], 80 82 capture_output=True,