anurag008w commited on
Commit
960fd80
·
1 Parent(s): 197a069

fix: triple key picking bug - harden error scoping, balance sticky keys, fix http headers

Browse files
Files changed (1) hide show
  1. openclaw-sync.py +61 -18
openclaw-sync.py CHANGED
@@ -127,31 +127,53 @@ def count_files(path: Path) -> int:
127
 
128
 
129
  def copy_state_entry_with_retry(source_path: Path, backup_path: Path, attempts: int = 3) -> None:
130
- """Copy one top-level .openclaw entry with short retries for hot files/dirs."""
 
 
 
 
 
 
131
  last_exc: Exception | None = None
 
132
  for attempt in range(1, attempts + 1):
133
- try:
134
- if backup_path.exists():
135
- if backup_path.is_dir():
136
- shutil.rmtree(backup_path, ignore_errors=True)
 
 
137
  else:
138
- backup_path.unlink(missing_ok=True)
 
139
  if source_path.is_dir():
140
- shutil.copytree(source_path, backup_path)
141
- return
142
- if source_path.is_file():
143
- shutil.copy2(source_path, backup_path)
 
144
  return
 
 
 
 
 
 
 
 
 
145
  return
146
  except Exception as exc:
147
  last_exc = exc
 
 
 
 
 
 
 
148
  if attempt < attempts:
149
  time.sleep(0.2 * attempt)
150
- if backup_path.exists():
151
- if backup_path.is_dir():
152
- shutil.rmtree(backup_path, ignore_errors=True)
153
- else:
154
- backup_path.unlink(missing_ok=True)
155
  continue
156
  raise last_exc
157
 
@@ -682,6 +704,7 @@ def restore_workspace() -> bool:
682
  def _sync_once_unlocked(
683
  last_fingerprint: str | None = None,
684
  last_marker: WorkspaceMarker | None = None,
 
685
  ) -> tuple[str, WorkspaceMarker]:
686
  if not HF_TOKEN:
687
  write_status("disabled", "HF_TOKEN is not configured.")
@@ -692,7 +715,15 @@ def _sync_once_unlocked(
692
  had_snapshot_copy_failures = snapshot_state_into_workspace()
693
  repo_id = ensure_repo_exists()
694
  current_marker = metadata_marker(WORKSPACE)
695
- if last_marker is not None and current_marker == last_marker and not _prune_needed:
 
 
 
 
 
 
 
 
696
  write_status("synced", "No workspace changes detected.")
697
  return (last_fingerprint or "", current_marker)
698
 
@@ -759,12 +790,17 @@ def _sync_once_unlocked(
759
  def sync_once(
760
  last_fingerprint: str | None = None,
761
  last_marker: WorkspaceMarker | None = None,
 
762
  ) -> tuple[str, WorkspaceMarker]:
763
  SYNC_LOCK_FILE.parent.mkdir(parents=True, exist_ok=True)
764
  with SYNC_LOCK_FILE.open("w", encoding="utf-8") as lock_handle:
765
  fcntl.flock(lock_handle, fcntl.LOCK_EX)
766
  try:
767
- return _sync_once_unlocked(last_fingerprint, last_marker)
 
 
 
 
768
  finally:
769
  fcntl.flock(lock_handle, fcntl.LOCK_UN)
770
 
@@ -974,7 +1010,14 @@ def loop() -> int:
974
  while not STOP_EVENT.is_set():
975
  try:
976
  sync_started_config_marker = file_marker(OPENCLAW_CONFIG_FILE)
977
- last_fingerprint, last_marker = sync_once(last_fingerprint, last_marker)
 
 
 
 
 
 
 
978
  if sync_trigger == "sessions":
979
  last_sessions_sync_time = time.monotonic()
980
  config_marker = file_marker(OPENCLAW_CONFIG_FILE)
 
127
 
128
 
129
  def copy_state_entry_with_retry(source_path: Path, backup_path: Path, attempts: int = 3) -> None:
130
+ """Copy one top-level .openclaw entry with short retries for hot files/dirs.
131
+
132
+ The state staging dir is seeded from the last known-good backup before this
133
+ function runs. Never delete that seeded entry until a fresh copy has fully
134
+ succeeded; hot session files can change mid-copy, and a failed copy must not
135
+ turn into a deletion that later prunes valid remote session data.
136
+ """
137
  last_exc: Exception | None = None
138
+ parent = backup_path.parent
139
  for attempt in range(1, attempts + 1):
140
+ tmp_path = parent / f".{backup_path.name}.snapshot-tmp-{os.getpid()}-{attempt}"
141
+ old_path = parent / f".{backup_path.name}.snapshot-old-{os.getpid()}-{attempt}"
142
+ for cleanup_path in (tmp_path, old_path):
143
+ if cleanup_path.exists():
144
+ if cleanup_path.is_dir():
145
+ shutil.rmtree(cleanup_path, ignore_errors=True)
146
  else:
147
+ cleanup_path.unlink(missing_ok=True)
148
+ try:
149
  if source_path.is_dir():
150
+ shutil.copytree(source_path, tmp_path)
151
+ elif source_path.is_file():
152
+ tmp_path.parent.mkdir(parents=True, exist_ok=True)
153
+ shutil.copy2(source_path, tmp_path)
154
+ else:
155
  return
156
+
157
+ if backup_path.exists():
158
+ backup_path.rename(old_path)
159
+ tmp_path.rename(backup_path)
160
+ if old_path.exists():
161
+ if old_path.is_dir():
162
+ shutil.rmtree(old_path, ignore_errors=True)
163
+ else:
164
+ old_path.unlink(missing_ok=True)
165
  return
166
  except Exception as exc:
167
  last_exc = exc
168
+ if tmp_path.exists():
169
+ if tmp_path.is_dir():
170
+ shutil.rmtree(tmp_path, ignore_errors=True)
171
+ else:
172
+ tmp_path.unlink(missing_ok=True)
173
+ if old_path.exists() and not backup_path.exists():
174
+ old_path.rename(backup_path)
175
  if attempt < attempts:
176
  time.sleep(0.2 * attempt)
 
 
 
 
 
177
  continue
178
  raise last_exc
179
 
 
704
  def _sync_once_unlocked(
705
  last_fingerprint: str | None = None,
706
  last_marker: WorkspaceMarker | None = None,
707
+ force_fingerprint_check: bool = False,
708
  ) -> tuple[str, WorkspaceMarker]:
709
  if not HF_TOKEN:
710
  write_status("disabled", "HF_TOKEN is not configured.")
 
715
  had_snapshot_copy_failures = snapshot_state_into_workspace()
716
  repo_id = ensure_repo_exists()
717
  current_marker = metadata_marker(WORKSPACE)
718
+ # Session watcher uses content digests and can detect same-size rewrites
719
+ # whose workspace metadata marker is unchanged. In that case, force the
720
+ # stronger fingerprint pass instead of returning early on metadata alone.
721
+ if (
722
+ last_marker is not None
723
+ and current_marker == last_marker
724
+ and not _prune_needed
725
+ and not force_fingerprint_check
726
+ ):
727
  write_status("synced", "No workspace changes detected.")
728
  return (last_fingerprint or "", current_marker)
729
 
 
790
  def sync_once(
791
  last_fingerprint: str | None = None,
792
  last_marker: WorkspaceMarker | None = None,
793
+ force_fingerprint_check: bool = False,
794
  ) -> tuple[str, WorkspaceMarker]:
795
  SYNC_LOCK_FILE.parent.mkdir(parents=True, exist_ok=True)
796
  with SYNC_LOCK_FILE.open("w", encoding="utf-8") as lock_handle:
797
  fcntl.flock(lock_handle, fcntl.LOCK_EX)
798
  try:
799
+ return _sync_once_unlocked(
800
+ last_fingerprint,
801
+ last_marker,
802
+ force_fingerprint_check=force_fingerprint_check,
803
+ )
804
  finally:
805
  fcntl.flock(lock_handle, fcntl.LOCK_UN)
806
 
 
1010
  while not STOP_EVENT.is_set():
1011
  try:
1012
  sync_started_config_marker = file_marker(OPENCLAW_CONFIG_FILE)
1013
+ last_fingerprint, last_marker = sync_once(
1014
+ last_fingerprint,
1015
+ last_marker,
1016
+ # A sessions trigger came from sessions_marker(), which hashes
1017
+ # session contents. Bypass the metadata-only fast path so
1018
+ # same-size session rewrites still reach the dataset.
1019
+ force_fingerprint_check=sync_trigger == "sessions",
1020
+ )
1021
  if sync_trigger == "sessions":
1022
  last_sessions_sync_time = time.monotonic()
1023
  config_marker = file_marker(OPENCLAW_CONFIG_FILE)