Fix hash computation to exclude version/tags metadata

The publish dialog adds version and tags fields that aren't stored in
the local config.yaml. Exclude these from hash computation so the
hash comparison works correctly.

Also use Tool.to_dict() for consistent hash computation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-01-16 08:02:42 -04:00
parent f1c462146b
commit 7f976d3f8c
2 changed files with 23 additions and 4 deletions

View File

@ -46,8 +46,14 @@ def get_tool_publish_state(tool_name: str) -> Tuple[str, Optional[str]]:
if not registry_hash: if not registry_hash:
return ("local", None) return ("local", None)
# Compute current hash (excluding hash fields) # Compute hash the same way as publish: load Tool object and use to_dict()
current_hash = compute_config_hash(config) # This ensures we compare the same normalized representation
tool = load_tool(tool_name)
if tool:
current_hash = compute_config_hash(tool.to_dict())
else:
# Fallback to raw config if Tool can't be loaded
current_hash = compute_config_hash(config)
if current_hash != registry_hash: if current_hash != registry_hash:
return ("modified", registry_hash) return ("modified", registry_hash)
@ -473,15 +479,23 @@ class ToolsPage(QWidget):
# Get status from registry # Get status from registry
status_data = client.get_my_tool_status(tool_name) status_data = client.get_my_tool_status(tool_name)
new_status = status_data.get("status", "pending") new_status = status_data.get("status", "pending")
new_hash = status_data.get("config_hash")
# Update local config # Update local config
config_data = yaml.safe_load(config_path.read_text()) or {} config_data = yaml.safe_load(config_path.read_text()) or {}
old_status = config_data.get("registry_status", "pending") old_status = config_data.get("registry_status", "pending")
old_hash = config_data.get("registry_hash")
changed = False
if old_status != new_status: if old_status != new_status:
config_data["registry_status"] = new_status config_data["registry_status"] = new_status
config_path.write_text(yaml.dump(config_data, default_flow_style=False, sort_keys=False)) changed = True
if new_hash and old_hash != new_hash:
config_data["registry_hash"] = new_hash
changed = True
if changed:
config_path.write_text(yaml.dump(config_data, default_flow_style=False, sort_keys=False))
self.refresh() self.refresh()
if new_status == "approved": if new_status == "approved":
self.main_window.show_status(f"Tool '{tool_name}' has been approved!") self.main_window.show_status(f"Tool '{tool_name}' has been approved!")

View File

@ -26,7 +26,12 @@ def compute_config_hash(config: Dict[str, Any], exclude_fields: Optional[list] =
Hash string in format "sha256:<64-char-hex>" Hash string in format "sha256:<64-char-hex>"
""" """
if exclude_fields is None: if exclude_fields is None:
exclude_fields = ['published_hash', 'registry_hash'] # Exclude registry metadata and publication metadata from hash
# These fields are added during publish but aren't part of tool content
exclude_fields = [
'published_hash', 'registry_hash', 'registry_status', # registry metadata
'version', 'tags', # publication metadata (added by publish dialog)
]
# Create a copy without excluded fields # Create a copy without excluded fields
config_copy = {k: v for k, v in config.items() if k not in exclude_fields} config_copy = {k: v for k, v in config.items() if k not in exclude_fields}