Add deployment safeguards to prevent code mismatch issues

- Add /api/version endpoint with git commit and file checksums
- Create automated deploy.ps1 script with pre-flight checks
- Document file dependencies to prevent partial deployments
- Add version verification before and after deployment

Prevents: 4-hour debugging sessions due to production/local mismatch
Ensures: All dependent files deploy together atomically
Verifies: Production matches local code after deployment
This commit is contained in:
2026-01-18 15:13:47 -07:00
parent a534a72a0f
commit a6eedc1b77
4 changed files with 441 additions and 0 deletions

View File

@@ -36,6 +36,7 @@ from api.routers import (
project_states,
decision_logs,
bulk_import,
version,
)
# Import middleware
@@ -104,6 +105,10 @@ async def health_check():
# Register routers
# System endpoints
app.include_router(version.router, prefix="/api", tags=["System"])
# Entity endpoints
app.include_router(machines.router, prefix="/api/machines", tags=["Machines"])
app.include_router(clients.router, prefix="/api/clients", tags=["Clients"])
app.include_router(sites.router, prefix="/api/sites", tags=["Sites"])

91
api/routers/version.py Normal file
View File

@@ -0,0 +1,91 @@
"""
Version endpoint for ClaudeTools API.
Returns version information to detect code mismatches.
"""
from fastapi import APIRouter
from datetime import datetime
import subprocess
import os
router = APIRouter()
@router.get(
"/version",
response_model=dict,
summary="Get API version information",
description="Returns version, git commit, and deployment timestamp",
)
def get_version():
"""
Get API version information.
Returns:
dict: Version info including git commit, branch, deployment time
"""
version_info = {
"api_version": "1.0.0",
"component": "claudetools-api",
"deployment_timestamp": datetime.utcnow().isoformat() + "Z"
}
# Try to get git information
try:
# Get current commit hash
result = subprocess.run(
["git", "rev-parse", "HEAD"],
capture_output=True,
text=True,
timeout=5,
cwd=os.path.dirname(os.path.dirname(__file__))
)
if result.returncode == 0:
version_info["git_commit"] = result.stdout.strip()
version_info["git_commit_short"] = result.stdout.strip()[:7]
# Get current branch
result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
capture_output=True,
text=True,
timeout=5,
cwd=os.path.dirname(os.path.dirname(__file__))
)
if result.returncode == 0:
version_info["git_branch"] = result.stdout.strip()
# Get last commit date
result = subprocess.run(
["git", "log", "-1", "--format=%ci"],
capture_output=True,
text=True,
timeout=5,
cwd=os.path.dirname(os.path.dirname(__file__))
)
if result.returncode == 0:
version_info["last_commit_date"] = result.stdout.strip()
except Exception:
version_info["git_info"] = "Not available (not a git repository)"
# Add file checksums for critical files
import hashlib
critical_files = [
"api/routers/conversation_contexts.py",
"api/services/conversation_context_service.py"
]
checksums = {}
base_dir = os.path.dirname(os.path.dirname(__file__))
for file_path in critical_files:
full_path = os.path.join(base_dir, file_path)
try:
with open(full_path, 'rb') as f:
checksums[file_path] = hashlib.md5(f.read()).hexdigest()[:8]
except Exception:
checksums[file_path] = "not_found"
version_info["file_checksums"] = checksums
return version_info