luv-cli 0.0.18__tar.gz → 0.0.19__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: luv-cli
3
- Version: 0.0.18
3
+ Version: 0.0.19
4
4
  Summary: Launch Claude Code agents on GitHub repos with isolated workspaces and optional Docker dev environments
5
5
  Project-URL: Homepage, https://github.com/exospherehost/luv
6
6
  Project-URL: Repository, https://github.com/exospherehost/luv
@@ -3,6 +3,7 @@ import os
3
3
  import random
4
4
  import re
5
5
  import shutil
6
+ import stat
6
7
  import subprocess
7
8
  import sys
8
9
  import tempfile
@@ -399,6 +400,23 @@ def launch(clone_dir: Path, prompt: str | None, plan_mode: bool = False,
399
400
  SAFE_AGE_SECONDS = 24 * 3600
400
401
 
401
402
 
403
+ def _on_rm_error(func, path, _exc):
404
+ """rmtree handler: make `path` (and its parent dir) writable, then retry."""
405
+ parent = os.path.dirname(path)
406
+ try:
407
+ os.chmod(parent, os.stat(parent).st_mode | stat.S_IWUSR | stat.S_IXUSR)
408
+ except OSError:
409
+ pass
410
+ os.chmod(path, stat.S_IWUSR | stat.S_IRUSR | stat.S_IXUSR)
411
+ func(path)
412
+
413
+
414
+ def _force_rmtree(path: Path) -> None:
415
+ """rmtree that chmods read-only files (e.g. uv's CACHEDIR.TAG) instead of crashing."""
416
+ kwargs = {"onexc": _on_rm_error} if sys.version_info >= (3, 12) else {"onerror": _on_rm_error}
417
+ shutil.rmtree(path, **kwargs)
418
+
419
+
402
420
  def cmd_clean(force: bool = False, safe: bool = False) -> None:
403
421
  """Scan ~/prs/ and delete fully-pushed, clean work folders."""
404
422
  if not PRS_DIR.exists():
@@ -421,7 +439,7 @@ def cmd_clean(force: bool = False, safe: bool = False) -> None:
421
439
  if safe and (now - entry.stat().st_mtime) < SAFE_AGE_SECONDS:
422
440
  skipped.append((entry.name, "younger than 24h (--safe)"))
423
441
  continue
424
- shutil.rmtree(entry)
442
+ _force_rmtree(entry)
425
443
  cleaned.append(entry.name)
426
444
  continue
427
445
 
@@ -464,7 +482,7 @@ def cmd_clean(force: bool = False, safe: bool = False) -> None:
464
482
  if local_sha != pr_head_sha:
465
483
  skipped.append((entry.name, "local HEAD differs from merged PR head"))
466
484
  continue
467
- shutil.rmtree(entry)
485
+ _force_rmtree(entry)
468
486
  cleaned.append(entry.name)
469
487
  continue
470
488
 
@@ -474,7 +492,7 @@ def cmd_clean(force: bool = False, safe: bool = False) -> None:
474
492
  skipped.append((entry.name, "unpushed commits"))
475
493
  continue
476
494
 
477
- shutil.rmtree(entry)
495
+ _force_rmtree(entry)
478
496
  cleaned.append(entry.name)
479
497
 
480
498
  if skipped:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "luv-cli"
7
- version = "0.0.18"
7
+ version = "0.0.19"
8
8
  description = "Launch Claude Code agents on GitHub repos with isolated workspaces and optional Docker dev environments"
9
9
  requires-python = ">=3.10"
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes