Skip to content

Fix database error handling in project creation - #79

Merged
frankbria merged 3 commits into
mainfrom
claude/fix-db-error-handling-01TjfxMoUDF1mGkyob7b94Af
Dec 11, 2025
Merged

Fix database error handling in project creation#79
frankbria merged 3 commits into
mainfrom
claude/fix-db-error-handling-01TjfxMoUDF1mGkyob7b94Af

Conversation

@frankbria

@frankbria frankbria commented Dec 10, 2025

Copy link
Copy Markdown
Owner
  • Add sqlite3 import and try-except blocks around database calls in create_project endpoint (list_projects, create_project, get_project)
  • Replace skipped database error test with 4 mocked tests using unittest.mock.patch to simulate SQLite errors:
    • test_create_project_database_locked_error
    • test_create_project_disk_full_error
    • test_create_project_integrity_error
    • test_create_project_list_projects_database_error
  • All database errors now return HTTP 500 with descriptive error messages
  • All 15 tests in test_project_creation_api.py pass

This ensures database failures are handled gracefully instead of causing crashes, improving API reliability and user experience.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved error handling during project creation to gracefully manage database-related failures and prevent incomplete states.
    • Enhanced cleanup procedures to ensure partially created projects and associated workspaces are properly removed when errors occur.
  • Tests

    • Added comprehensive database error scenario tests to verify system reliability and proper error responses.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add sqlite3 import and try-except blocks around database calls in
  create_project endpoint (list_projects, create_project, get_project)
- Replace skipped database error test with 4 mocked tests using
  unittest.mock.patch to simulate SQLite errors:
  - test_create_project_database_locked_error
  - test_create_project_disk_full_error
  - test_create_project_integrity_error
  - test_create_project_list_projects_database_error
- All database errors now return HTTP 500 with descriptive error messages
- All 15 tests in test_project_creation_api.py pass

This ensures database failures are handled gracefully instead of causing
crashes, improving API reliability and user experience.
@coderabbitai

coderabbitai Bot commented Dec 10, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds explicit sqlite3-based error handling around database interactions in the project creation endpoint, updates workspace creation flow to capture workspace paths, and enhances failure handling with cleanup logic. Corresponding test coverage for database error scenarios during project creation is added.

Changes

Cohort / File(s) Summary
Database error handling and cleanup
codeframe/ui/server.py
Wraps database calls (list_projects, create_project) in try/except sqlite3.Error blocks; on failure, raises HTTP 500. Updates workspace creation to use workspace_manager and capture paths. Implements cleanup logic (remove workspace directory, delete project) on failures. Adds sqlite3 import for explicit error handling.
Database error scenario tests
tests/api/test_project_creation_api.py
Replaces skipped test with multiple concrete test cases simulating database errors (OperationalError, IntegrityError, disk-full scenarios) via mocking server.app.state.db methods. Verifies HTTP 500 responses with database-related error details.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–25 minutes

  • Verify all cleanup paths (workspace directory removal, project deletion) execute consistently in all failure branches
  • Check that each sqlite3.Error catch properly converts to HTTP 500 and includes appropriate error messaging
  • Confirm test mocking coverage is comprehensive and accurately simulates the error scenarios being handled
  • Validate that workspace_path capture and database update logic maintain transaction consistency

Possibly related PRs

Poem

🐰 A rabbit hops through databases with care,
With try-and-except blocks everywhere,
When errors arise from disk and from store,
We clean up our workspace and close the door,
Error paths tested, no chaos to spare! 🌟

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix database error handling in project creation' directly and clearly summarizes the main change: adding error handling for database operations in the project creation flow.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/fix-db-error-handling-01TjfxMoUDF1mGkyob7b94Af

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented Dec 10, 2025

Copy link
Copy Markdown

Code Review: Database Error Handling in Project Creation

Summary

This PR adds graceful error handling for SQLite database operations in the project creation API endpoint. The changes prevent crashes and provide meaningful error messages to users when database operations fail.


✅ Strengths

  1. Comprehensive Error Coverage: The PR addresses all three database calls in the create_project endpoint:

    • list_projects() (duplicate name check)
    • create_project() (project record creation)
    • get_project() (returning project details)
  2. Well-Tested: Replaced a skipped test with 4 specific mocked tests covering different SQLite error scenarios:

    • Database locked errors
    • Disk I/O errors
    • Integrity constraint violations
    • Errors during list operations
  3. Appropriate Error Messages: Uses descriptive error messages that help with debugging (f"Database error: {str(e)}")

  4. Proper HTTP Status Code: Returns 500 Internal Server Error, which is correct for database failures


⚠️ Issues & Recommendations

1. CRITICAL: Missing Error Handling in Cleanup Path

Location: server.py:363-369

The update_project() and delete_project() calls in the workspace creation exception handler are not wrapped in try-except blocks:

except Exception as e:
    # Cleanup: delete project and workspace if creation fails
    app.state.db.delete_project(project_id)  # ⚠️ No error handling!

Risk: If the database is locked/unavailable during cleanup, this will raise an unhandled exception, preventing proper error response to the client.

Recommendation:

except Exception as e:
    # Cleanup: delete project and workspace if creation fails
    try:
        app.state.db.delete_project(project_id)
    except sqlite3.Error as db_error:
        logger.error(f"Failed to delete project {project_id} during cleanup: {db_error}")
    
    # ... rest of cleanup

Similarly, the update_project() call at line 363 should be wrapped.


2. Inconsistent Error Handling Pattern

Location: server.py:367-382

The outer exception handler catches Exception (too broad), while the new database handlers catch sqlite3.Error (specific). This creates inconsistency.

Recommendation: The outer handler at line 367 could be more specific:

except sqlite3.Error as e:
    # Handle database errors during workspace update
    ...
except Exception as e:
    # Handle workspace creation errors
    ...

This would separate database failures from workspace creation failures for better error reporting.


3. Missing Error Handling on Line 363

Location: server.py:363-365

The update_project() call that sets the workspace path is not wrapped in a try-except block:

# Update project with workspace path and git status
app.state.db.update_project(  # ⚠️ No error handling!
    project_id, {"workspace_path": str(workspace_path), "git_initialized": True}
)

Risk: Database errors here will propagate as unhandled exceptions instead of clean 500 responses.

Recommendation:

try:
    app.state.db.update_project(
        project_id, {"workspace_path": str(workspace_path), "git_initialized": True}
    )
except sqlite3.Error as e:
    # Rollback: delete project and workspace
    try:
        app.state.db.delete_project(project_id)
    except sqlite3.Error:
        pass  # Best effort cleanup
    
    raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")

4. Test Coverage Gap

The tests mock create_project() and list_projects(), but don't test:

  • Errors in update_project() (line 363)
  • Errors in get_project() when returning results (line 386)
  • Errors in delete_project() during cleanup (line 369)

Recommendation: Add tests for these scenarios to ensure complete coverage.


5. Database Connection Context

Observation: The codebase uses synchronous SQLite (sqlite3), but CLAUDE.md mentions aiosqlite for async support in multiple features.

Question: Should this endpoint be using async database operations? The function signature is async def create_project(), but all database calls are synchronous, which could block the event loop.

Recommendation: Consider using aiosqlite consistently across the codebase, or if sync SQLite is intentional, ensure it's not blocking async operations.


6. Error Message Exposure

Security Consideration: The error messages expose raw SQLite error text to clients:

raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")

Risk: In production, exposing internal error details can leak information about database schema, file paths, or system state.

Recommendation: For production deployments, consider logging the full error but returning a generic message:

logger.error(f"Database error in create_project: {str(e)}")
raise HTTPException(status_code=500, detail="An internal database error occurred")

📊 Test Quality

Strengths:

  • Tests use proper mocking with unittest.mock.patch
  • Cover multiple SQLite error types (OperationalError, IntegrityError)
  • Verify both status code and error message content

Suggestions:

  • Add a test for get_project() failure (currently only create_project and list_projects are tested)
  • Consider adding a test that verifies cleanup behavior when update_project() fails

🎯 Overall Assessment

Rating: ⭐⭐⭐ (3/5 - Good foundation, needs critical fixes)

Verdict: This PR makes important improvements to error handling, but has critical gaps in the cleanup path and workspace update logic. The changes are directionally correct but incomplete.

Recommendation: Request changes - Address the missing error handling in:

  1. update_project() call (line 363)
  2. delete_project() cleanup call (line 369)
  3. Add corresponding test coverage

Once these are addressed, this will be a solid improvement to API reliability.


📝 Suggested Next Steps

  1. Add error handling to update_project() and delete_project() calls
  2. Add tests for the uncovered error scenarios
  3. Consider the async/sync database pattern consistency
  4. Review error message exposure for production deployments

Let me know if you'd like help implementing any of these suggestions!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
tests/api/test_project_creation_api.py (2)

12-13: Consider pytest fixtures over unittest.mock for database testing.

The tests use unittest.mock.patch to simulate database errors, but this approach doesn't test the actual database error handling flow. Consider using pytest fixtures with a real database to trigger actual sqlite3 errors, which would provide more comprehensive coverage and align with the project's testing patterns.

Based on learnings, the codebase prefers pytest fixtures and avoiding over-mocking.

</review_comment_end>


218-293: Consider adding test coverage for workspace creation errors.

The new tests cover database errors during list_projects, create_project, and get_project, but the create_project endpoint also includes workspace creation logic (lines 354-382 in server.py) that can fail. Consider adding a test that verifies error handling when workspace creation fails after the project is created in the database.

</review_comment_end>

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 612f258 and 197ce43.

📒 Files selected for processing (2)
  • codeframe/ui/server.py (3 hunks)
  • tests/api/test_project_creation_api.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Python 3.11+ with async/await syntax, asyncio, and AsyncAnthropic for AI integrations in backend code
Lint code with ruff check . before committing; fix all style violations to maintain consistent code quality across the Python codebase
Use type hints with Python 3.11+ throughout all Python code; run type checking as part of quality gates to catch type errors before task completion

Files:

  • tests/api/test_project_creation_api.py
  • codeframe/ui/server.py
🧠 Learnings (3)
📚 Learning: 2025-11-25T19:08:37.203Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Applies to docs/tests/**/*.py : Use pytest fixtures for Python testing and avoid over-mocking

Applied to files:

  • tests/api/test_project_creation_api.py
📚 Learning: 2025-12-05T05:44:48.066Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T05:44:48.066Z
Learning: Applies to tests/test_*worker_agent.py : Run async worker agent tests with pytest using: pytest tests/test_*worker_agent.py; ensure all async context methods are properly tested with database fixtures

Applied to files:

  • tests/api/test_project_creation_api.py
📚 Learning: 2025-12-05T05:44:48.066Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T05:44:48.066Z
Learning: Applies to codeframe/persistence/database.py : Use SQLite with async support (aiosqlite) for all database operations; maintain schema tables: context_items (with agent_id scoping), blockers, code_reviews, token_usage, tasks, checkpoints

Applied to files:

  • codeframe/ui/server.py
🧬 Code graph analysis (2)
tests/api/test_project_creation_api.py (1)
tests/api/conftest.py (1)
  • api_client (42-89)
codeframe/ui/server.py (1)
codeframe/persistence/database.py (3)
  • list_projects (1093-1117)
  • create_project (549-594)
  • get_project (596-601)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Backend Unit Tests
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: claude-review
🔇 Additional comments (1)
codeframe/ui/server.py (1)

16-16: Consider migrating to async database operations.

The codebase uses synchronous sqlite3 operations, but the learning notes recommend using aiosqlite for async support. This PR adds error handling for sync operations, which is appropriate for the current implementation. However, consider planning a future migration to async database operations for better concurrency and performance.

Based on learnings: "Use SQLite with async support (aiosqlite) for all database operations."

</review_comment_end>

Comment thread codeframe/ui/server.py
Comment thread codeframe/ui/server.py Outdated
Comment thread codeframe/ui/server.py Outdated
Comment on lines +218 to +236
def test_create_project_database_locked_error(self, api_client):
"""Test that database locked error returns 500 Internal Server Error."""
from codeframe.ui import server

with patch.object(
server.app.state.db,
"create_project",
side_effect=sqlite3.OperationalError("database is locked"),
):
response = api_client.post(
"/api/projects",
json={"name": "test-db-locked", "description": "Test project"},
)

assert response.status_code == 500
data = response.json()
assert "detail" in data
assert "database" in data["detail"].lower()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add type hints to test methods.

The test method lacks type hints for the api_client parameter. As per coding guidelines, use type hints throughout all Python code.

Apply this diff:

-    def test_create_project_database_locked_error(self, api_client):
+    def test_create_project_database_locked_error(self, api_client: TestClient) -> None:
         """Test that database locked error returns 500 Internal Server Error."""

You'll need to add this import at the top:

from starlette.testclient import TestClient

</review_comment_end>

🤖 Prompt for AI Agents
In tests/api/test_project_creation_api.py around lines 218 to 236, the test
method test_create_project_database_locked_error lacks a type hint for the
api_client parameter; add the type annotation api_client: TestClient to the
method signature and ensure TestClient is imported at the top of the file (from
starlette.testclient import TestClient) so the hint resolves.

Comment on lines +237 to +255
def test_create_project_disk_full_error(self, api_client):
"""Test that disk I/O error returns 500 Internal Server Error."""
from codeframe.ui import server

with patch.object(
server.app.state.db,
"create_project",
side_effect=sqlite3.OperationalError("disk I/O error"),
):
response = api_client.post(
"/api/projects",
json={"name": "test-disk-full", "description": "Test project"},
)

assert response.status_code == 500
data = response.json()
assert "detail" in data
assert "database" in data["detail"].lower() or "i/o" in data["detail"].lower()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add type hints to test methods.

Missing type hints for the api_client parameter.

Apply this diff:

-    def test_create_project_disk_full_error(self, api_client):
+    def test_create_project_disk_full_error(self, api_client: TestClient) -> None:
         """Test that disk I/O error returns 500 Internal Server Error."""

</review_comment_end>

🤖 Prompt for AI Agents
In tests/api/test_project_creation_api.py around lines 237 to 255, the test
method test_create_project_disk_full_error is missing a type hint for the
api_client parameter; update the signature to include the correct type hint
(e.g., api_client: TestClient or the project’s fixture type) so the test
function is properly typed, and run tests to ensure the import/type is available
or add an import/forward reference for the TestClient type if needed.

Comment on lines +256 to +274
def test_create_project_integrity_error(self, api_client):
"""Test that constraint violation error returns 500 Internal Server Error."""
from codeframe.ui import server

with patch.object(
server.app.state.db,
"create_project",
side_effect=sqlite3.IntegrityError("UNIQUE constraint failed"),
):
response = api_client.post(
"/api/projects",
json={"name": "test-integrity", "description": "Test project"},
)

assert response.status_code == 500
data = response.json()
assert "detail" in data
assert "database" in data["detail"].lower() or "constraint" in data["detail"].lower()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clarify IntegrityError test scenario and add type hints.

This test simulates an IntegrityError during create_project, but the duplicate name check at lines 335-338 in server.py should prevent most IntegrityError cases from reaching create_project. Consider clarifying the test scenario (e.g., race condition between duplicate check and insert) or documenting that this tests edge cases where the duplicate check is bypassed.

Also missing type hints for the api_client parameter.

Apply this diff:

-    def test_create_project_integrity_error(self, api_client):
-        """Test that constraint violation error returns 500 Internal Server Error."""
+    def test_create_project_integrity_error(self, api_client: TestClient) -> None:
+        """Test that constraint violation error returns 500 Internal Server Error.
+        
+        This tests edge cases where IntegrityError occurs despite duplicate name checks,
+        such as race conditions or other constraint violations.
+        """

</review_comment_end>

🤖 Prompt for AI Agents
In tests/api/test_project_creation_api.py around lines 256 to 274, clarify the
IntegrityError test by updating the docstring or adding an inline comment
stating this simulates an edge-case race condition where the duplicate-name
pre-check is bypassed (e.g., another insert occurs between check and insert),
and add a type hint for the api_client parameter (e.g., api_client: TestClient
or the appropriate fixture type) to the test signature so the test function is
typed.

Comment on lines +275 to 293
def test_create_project_list_projects_database_error(self, api_client):
"""Test that database error during list_projects returns 500 Internal Server Error."""
from codeframe.ui import server

with patch.object(
server.app.state.db,
"list_projects",
side_effect=sqlite3.OperationalError("database is locked"),
):
response = api_client.post(
"/api/projects",
json={"name": "test-list-error", "description": "Test project"},
)

assert response.status_code == 500
data = response.json()
assert "detail" in data
assert "database" in data["detail"].lower()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add type hints to test methods.

Missing type hints for the api_client parameter.

Apply this diff:

-    def test_create_project_list_projects_database_error(self, api_client):
+    def test_create_project_list_projects_database_error(self, api_client: TestClient) -> None:
         """Test that database error during list_projects returns 500 Internal Server Error."""

</review_comment_end>

🤖 Prompt for AI Agents
In tests/api/test_project_creation_api.py around lines 275 to 293, the test
function is missing a type hint for the api_client parameter; add an explicit
type annotation (for example api_client: TestClient) and import the matching
TestClient type (from fastapi.testclient import TestClient or the project's
fixture type) at the top of the file so the test signatures are fully typed and
consistent with other tests.

@claude

claude Bot commented Dec 11, 2025

Copy link
Copy Markdown

Code Review: Fix database error handling in project creation

Summary

This PR improves error handling for database operations in the project creation endpoint. The changes replace a skipped test with comprehensive mocked error scenarios and add proper try-except blocks around all database calls.

✅ Strengths

  1. Comprehensive Error Coverage: The PR adds try-except blocks around all 5 database operations in the create_project endpoint:

    • list_projects() (line 330-333)
    • create_project() (line 341-351)
    • update_project() (line 363-386)
    • delete_project() in cleanup paths (line 372-375, 393-396)
    • get_project() (line 412-415)
  2. Excellent Test Coverage: The new tests use unittest.mock.patch to simulate various SQLite errors:

    • Database locked (OperationalError)
    • Disk I/O errors (OperationalError)
    • Constraint violations (IntegrityError)
    • Coverage across all database call sites (5 new tests)
  3. Defensive Cleanup Logic: The nested try-except in the update_project() error handler (lines 367-386) demonstrates defense-in-depth:

    • Attempts to delete the partially created project
    • Attempts to remove the workspace directory
    • Logs cleanup failures without crashing
    • This prevents orphaned database records and directories
  4. Consistent Error Responses: All database errors return HTTP 500 with descriptive messages following REST best practices.

⚠️ Issues & Concerns

1. Information Disclosure Risk (Security)

Severity: Medium

The error messages expose raw SQLite error details to clients:

raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")

Problem: This can leak sensitive information like:

  • Database file paths
  • Schema details
  • Internal implementation details

Recommendation: Use generic error messages for production:

logger.error(f"Database error in create_project: {str(e)}")  # Detailed log
raise HTTPException(
    status_code=500, 
    detail="An internal database error occurred. Please try again later."
)

Detailed error information should only go to server logs, not API responses.

2. Incomplete Cleanup Path (Bug)

Severity: Medium

In the update_project() error handler (lines 367-386), the code deletes the project record and workspace directory. However, if workspace_path was successfully created (line 355-360), there may be additional state to clean up:

  • Git repository initialization
  • Workspace configuration files
  • Any hooks or event handlers triggered during workspace creation

Current code (line 378):

workspace_dir = app.state.workspace_manager.workspace_root / str(project_id)

Recommendation: Use the actual workspace_path returned from create_workspace() instead of reconstructing it:

# workspace_path is already available from line 355
if workspace_path.exists():
    try:
        shutil.rmtree(workspace_path)
        logger.info(f"Cleaned up workspace directory: {workspace_path}")
    except Exception as cleanup_fs_error:
        logger.error(f"Failed to clean up workspace {workspace_path}: {cleanup_fs_error}")

3. Inconsistent Exception Handling (Code Quality)

Severity: Low

The cleanup code catches sqlite3.Error for database operations (lines 372-375) but catches broad Exception for filesystem operations (lines 379-384). This is inconsistent with the specific exception handling used elsewhere in the PR.

Recommendation: Be more specific where possible:

except (OSError, PermissionError) as cleanup_fs_error:
    logger.error(f"Failed to clean up workspace {workspace_path}: {cleanup_fs_error}")

4. Test Assertion Could Be Stricter (Test Quality)

Severity: Low

Several tests use or assertions that are too permissive (lines 254, 273):

assert "database" in data["detail"].lower() or "i/o" in data["detail"].lower()

Problem: This would pass even if the error message is completely unrelated (e.g., "Error: something").

Recommendation: Assert on the exact expected behavior:

# For disk I/O errors, we expect "Database error: disk I/O error"
assert "database error" in data["detail"].lower()

5. Missing Edge Case Test (Test Coverage)

Severity: Low

There's no test for the scenario where delete_project() fails during cleanup. This could happen if:

  1. create_project() succeeds
  2. Workspace creation fails
  3. delete_project() in cleanup (line 394) also fails

Recommendation: Add a test that patches both create_workspace and delete_project to verify the error is still propagated correctly and doesn't crash.

📊 Performance Considerations

No performance concerns. The added try-except blocks have negligible overhead, and the tests use mocking to avoid actual database I/O.

🔒 Security Considerations

See Issue #1 above regarding information disclosure. The current implementation exposes too much detail about internal database errors.

📝 Best Practices Alignment

According to CLAUDE.md:

  • ✅ Uses Python 3.11+ features
  • ✅ Follows FastAPI patterns
  • ✅ SQLite error handling with aiosqlite (though this endpoint appears synchronous)
  • ⚠️ Error handling could be more production-ready (see Issue Add Claude Code GitHub Workflow #1)

🎯 Recommendations Summary

Must Fix (Before Merge):

  1. Sanitize error messages to avoid information disclosure (Issue Add Claude Code GitHub Workflow #1)

Should Fix (High Priority):
2. Use actual workspace_path variable in cleanup instead of reconstructing it (Issue #2)

Nice to Have:
3. Use specific exception types in filesystem cleanup (Issue #3)
4. Strengthen test assertions (Issue #4)
5. Add edge case test for nested cleanup failures (Issue #5)

📈 Overall Assessment

Score: 7/10

This is a solid improvement that significantly enhances the robustness of the project creation API. The test coverage is excellent, and the cleanup logic shows good defensive programming. However, the information disclosure risk should be addressed before merging to production.

Recommendation: Request changes to address security concern (#1), then approve.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (7)
tests/api/test_project_creation_api.py (4)

218-236: Add type hints to test method.

Missing type hints for the api_client parameter. As per coding guidelines, use type hints throughout all Python code.

Apply this diff:

-    def test_create_project_database_locked_error(self, api_client):
+    def test_create_project_database_locked_error(self, api_client: TestClient) -> None:
         """Test that database locked error returns 500 Internal Server Error."""

Add this import at the top of the file:

from starlette.testclient import TestClient

237-255: Add type hints to test method.

Missing type hints for the api_client parameter.

-    def test_create_project_disk_full_error(self, api_client):
+    def test_create_project_disk_full_error(self, api_client: TestClient) -> None:

256-274: Clarify IntegrityError test scenario and add type hints.

This test is valid for edge cases like race conditions where the duplicate check passes but a constraint violation occurs during insert. Consider clarifying this in the docstring. Also missing type hints.

-    def test_create_project_integrity_error(self, api_client):
-        """Test that constraint violation error returns 500 Internal Server Error."""
+    def test_create_project_integrity_error(self, api_client: TestClient) -> None:
+        """Test that constraint violation error returns 500 Internal Server Error.
+
+        Tests edge cases where IntegrityError occurs despite duplicate name checks,
+        such as race conditions or other constraint violations.
+        """

275-293: Add type hints to test method.

Missing type hints for the api_client parameter.

-    def test_create_project_list_projects_database_error(self, api_client):
+    def test_create_project_list_projects_database_error(self, api_client: TestClient) -> None:
codeframe/ui/server.py (3)

330-334: Sanitize error messages to avoid information disclosure.

The error handler exposes internal database error details directly to the API response. This could leak sensitive information about the database schema, file paths, or internal state.

Consider using a generic error message for production:

     try:
         existing_projects = app.state.db.list_projects()
     except sqlite3.Error as e:
-        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
+        logger.error(f"Database error in list_projects: {str(e)}")
+        raise HTTPException(status_code=500, detail="Failed to retrieve projects from database")

341-351: Sanitize error messages to avoid information disclosure.

Similar to the list_projects error handler, this exposes internal database error details. Use a generic error message and log the details instead.

     try:
         project_id = app.state.db.create_project(
             name=request.name,
             description=request.description,
             source_type=request.source_type.value,
             source_location=request.source_location,
             source_branch=request.source_branch,
             workspace_path="",
         )
     except sqlite3.Error as e:
-        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
+        logger.error(f"Database error in create_project: {str(e)}")
+        raise HTTPException(status_code=500, detail="Failed to create project in database")

412-415: Sanitize error messages to avoid information disclosure.

This error handler also exposes internal database details. Use a generic error message and log the details.

     try:
         project = app.state.db.get_project(project_id)
     except sqlite3.Error as e:
-        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
+        logger.error(f"Database error in get_project for project {project_id}: {str(e)}")
+        raise HTTPException(status_code=500, detail="Failed to retrieve project from database")
🧹 Nitpick comments (3)
tests/api/test_project_creation_api.py (2)

294-311: Good test coverage; add type hints.

This test properly covers the update_project error path added in server.py. However, it's missing type hints for consistency with coding guidelines.

-    def test_create_project_update_project_database_error(self, api_client):
+    def test_create_project_update_project_database_error(self, api_client: TestClient) -> None:

313-330: Good test coverage; add type hints.

This test properly covers the get_project error path. Missing type hints for consistency.

-    def test_create_project_get_project_database_error(self, api_client):
+    def test_create_project_get_project_database_error(self, api_client: TestClient) -> None:
codeframe/ui/server.py (1)

388-409: Improved cleanup handling; consider sanitizing workspace error message.

The cleanup logic properly wraps the delete_project call in a try-except to handle cases where the database error might occur during cleanup. The workspace path sanitization on line 409 could potentially leak internal paths.

For consistency with the other error handlers, consider sanitizing:

-        raise HTTPException(status_code=500, detail=f"Workspace creation failed: {str(e)}")
+        raise HTTPException(status_code=500, detail="Workspace creation failed")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 197ce43 and 18adf2b.

📒 Files selected for processing (2)
  • codeframe/ui/server.py (4 hunks)
  • tests/api/test_project_creation_api.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Python 3.11+ with async/await syntax, asyncio, and AsyncAnthropic for AI integrations in backend code
Lint code with ruff check . before committing; fix all style violations to maintain consistent code quality across the Python codebase
Use type hints with Python 3.11+ throughout all Python code; run type checking as part of quality gates to catch type errors before task completion

Files:

  • tests/api/test_project_creation_api.py
  • codeframe/ui/server.py
🧠 Learnings (3)
📚 Learning: 2025-11-25T19:08:37.203Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:08:37.203Z
Learning: Applies to docs/tests/**/*.py : Use pytest fixtures for Python testing and avoid over-mocking

Applied to files:

  • tests/api/test_project_creation_api.py
📚 Learning: 2025-12-05T05:44:48.066Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T05:44:48.066Z
Learning: Applies to tests/test_*worker_agent.py : Run async worker agent tests with pytest using: pytest tests/test_*worker_agent.py; ensure all async context methods are properly tested with database fixtures

Applied to files:

  • tests/api/test_project_creation_api.py
📚 Learning: 2025-12-05T05:44:48.066Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T05:44:48.066Z
Learning: Applies to codeframe/persistence/database.py : Use SQLite with async support (aiosqlite) for all database operations; maintain schema tables: context_items (with agent_id scoping), blockers, code_reviews, token_usage, tasks, checkpoints

Applied to files:

  • codeframe/ui/server.py
🧬 Code graph analysis (2)
tests/api/test_project_creation_api.py (1)
tests/api/conftest.py (1)
  • api_client (42-89)
codeframe/ui/server.py (1)
codeframe/persistence/database.py (5)
  • list_projects (1093-1117)
  • create_project (549-594)
  • update_project (1157-1189)
  • delete_project (1191-1199)
  • get_project (596-601)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: Backend Unit Tests
  • GitHub Check: claude-review
🔇 Additional comments (2)
tests/api/test_project_creation_api.py (1)

12-14: Imports are appropriate for the database error simulation approach.

The sqlite3 import enables raising specific SQLite exceptions, and unittest.mock.patch is standard for patching object methods. This approach is acceptable for simulating database errors that are difficult to trigger with real database operations.

codeframe/ui/server.py (1)

16-16: Import addition is appropriate.

The sqlite3 import is necessary for catching specific SQLite exceptions in the new error handling code.

Comment thread codeframe/ui/server.py Outdated
Addresses security concerns from code review:

**Issue #1 (SECURITY - MUST FIX): Information Disclosure Risk**
- Replace raw SQLite error exposure with generic user-facing messages
- Log detailed errors server-side for debugging
- Prevents leaking database paths, schema details, and internal state
- Changes:
  - list_projects(): "Database error occurred. Please try again later."
  - create_project(): "Database error occurred. Please try again later."
  - update_project(): "Database error occurred. Please try again later."
  - get_project(): "Database error occurred. Please try again later."
  - Workspace creation: "Workspace creation failed. Please try again later."

**Issue #2 (BUG - SHOULD FIX): Incomplete Cleanup Path**
- Use actual workspace_path variable from create_workspace() return value
- Previously reconstructed path: workspace_root / str(project_id)
- Now uses the actual path returned by WorkspaceManager
- Ensures correct cleanup even if path construction logic differs

**Issue #3 (CODE QUALITY): Inconsistent Exception Handling**
- Replace broad Exception with specific (OSError, PermissionError)
- Consistent with sqlite3.Error specificity used elsewhere
- Better error handling and debugging

**Fix: HTTPException Re-raising**
- Add explicit except HTTPException clause to prevent double-wrapping
- Database errors from update_project() now properly propagate
- Prevents "Workspace creation failed" masking "Database error"

Test results: 17/17 passing (100%)
All error messages now production-safe while maintaining debuggability
@claude

claude Bot commented Dec 11, 2025

Copy link
Copy Markdown

Code Review - PR #79: Fix database error handling in project creation

Summary

This PR adds comprehensive database error handling to the project creation endpoint with proper cleanup mechanisms and test coverage. Overall, this is a solid improvement to API reliability with good test coverage.


✅ Strengths

  1. Comprehensive Error Coverage: The PR adds try-catch blocks around all 5 database operations in the create_project endpoint:

    • list_projects() - line 331
    • create_project() - line 345
    • update_project() - line 370
    • get_project() - line 426
    • Plus cleanup calls to delete_project()
  2. Robust Cleanup Logic: The nested exception handling (lines 373-393) properly cleans up both database records AND filesystem state when update_project() fails. This prevents orphaned workspaces.

  3. Excellent Test Coverage: 6 new tests covering different failure scenarios:

    • Database locked errors
    • Disk I/O errors
    • Integrity constraint violations
    • Errors at each database operation point
  4. Consistent Error Messages: User-facing error messages are generic ("Database error occurred. Please try again later.") while detailed errors are logged server-side - good security practice.


🔍 Issues & Suggestions

1. Inconsistent Exception Handling Pattern (Minor)

Location: server.py:395-397

except HTTPException:
    # Re-raise HTTPException from database error handling above
    raise

Issue: This pattern only re-raises HTTPException from the update_project() database error. If workspace_path creation succeeds but other exceptions occur, they fall through to the generic handler below.

Suggestion: This is actually correct as-is, but the comment could be clearer:

except HTTPException:
    # Re-raise HTTPException from update_project database error (already cleaned up)
    raise

2. Missing Error Handling in Other Endpoints (Medium Priority)

Locations: Other endpoints also call database methods without error handling:

  • server.py:308 - app.state.db.list_projects() in list_projects endpoint
  • server.py:462 - app.state.db.get_project() in start_project_agent
  • server.py:490 - app.state.db.get_project() in get_project_status
  • server.py:496 - app.state.db._calculate_project_progress()
  • server.py:529-534 - Multiple DB calls in list_agents
  • Plus 6 more in assign_agent, remove_agent_assignment, and update_agent_role

Suggestion: While this PR focuses on project creation (and does it well), consider adding similar error handling to other endpoints in a follow-up PR to ensure consistent error handling across the API.


3. Test Assertion Could Be More Specific (Minor)

Location: test_project_creation_api.py:254

assert "database" in data["detail"].lower() or "i/o" in data["detail"].lower()

Issue: The assertion for disk_full_error checks for either "database" OR "i/o", but the actual error message only contains "database" (same generic message as other errors). This test would pass even if the error handling was wrong.

Suggestion: Since all database errors return the same generic message, simplify to:

assert "database" in data["detail"].lower()

Same applies to line 273 in test_create_project_integrity_error.


4. Potential Race Condition in Cleanup (Low Priority)

Location: server.py:412

workspace_path = app.state.workspace_manager.workspace_root / str(project_id)
if workspace_path.exists():
    try:
        shutil.rmtree(workspace_path)

Issue: If workspace creation partially succeeds but uses a different path than expected, this hardcoded path reconstruction might miss it. (Though this is unlikely in practice given the current WorkspaceManager implementation.)

Suggestion: Consider capturing workspace_path from the successful workspace creation earlier (you already do this on line 361), so cleanup uses the exact same path. Currently you have two different path references (line 361 vs 412).


5. Documentation Could Mention Cleanup Behavior (Minor)

Location: server.py:313-322 (docstring)

Suggestion: Add a note in the docstring about transactional behavior:

"""Create a new project with automatic workspace initialization.

Creates a project record and workspace atomically. On failure, automatically
cleans up any partial state (database records + filesystem).

Args:
    request: Project creation request with name, description, source details
    
Returns:
    Created project details
    
Raises:
    HTTPException: 409 if project name already exists
    HTTPException: 500 if database or workspace creation fails
"""

🔒 Security Considerations

No security issues found. Generic error messages prevent information leakage while detailed errors are logged server-side.


🚀 Performance Considerations

No performance impact. Exception handling has negligible overhead in the happy path.


📊 Test Coverage

Excellent test coverage with 6 new mocked database error tests covering all failure points. The tests properly use unittest.mock.patch to simulate SQLite errors without actual database corruption.

Suggested addition: Consider adding one integration test that actually triggers a real database error (e.g., by setting database file to read-only) to validate the full error handling path. The current tests only mock the database methods.


📝 Final Recommendation

Approve with minor suggestions. This PR significantly improves error handling and API reliability. The issues noted above are minor and could be addressed in follow-up PRs.

Priority follow-ups:

  1. Add similar error handling to other endpoints (medium priority)
  2. Simplify test assertions for consistency (low priority)
  3. Consider integration test for real database errors (low priority)

Great work on the comprehensive test coverage! 🎉

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
codeframe/ui/server.py (4)

329-336: Good, DB errors during duplicate-name pre-check are now sanitized and logged

Catching sqlite3.Error, logging the details, and returning a generic 500 response avoids leaking DB internals while still failing fast when the project list can’t be read. Consider adding exc_info=True to capture stack traces for debugging:

-    except sqlite3.Error as e:
-        logger.error(f"Database error listing projects: {str(e)}")
+    except sqlite3.Error as e:
+        logger.error(f"Database error listing projects: {e}", exc_info=True)

This is optional but improves diagnosability.


344-357: Create-project insert error handling is correct; optional refinement for logging

Wrapping app.state.db.create_project in a sqlite3.Error handler and returning a generic 500 with a user-friendly message is aligned with the PR goal and avoids exposing raw DB messages. As above, you could log with exc_info=True for better traces:

-    except sqlite3.Error as e:
-        logger.error(f"Database error creating project: {str(e)}")
+    except sqlite3.Error as e:
+        logger.error(f"Database error creating project: {e}", exc_info=True)

Functionally this block looks solid.


399-422: Workspace-creation failure path is defensive and well-contained

Catching generic exceptions from create_workspace, logging, then best-effort deleting the project row and workspace directory before raising a sanitized 500 gives a clean failure mode for callers without leaking implementation details. Using workspace_root / str(project_id) as a fallback path for cleanup is a reasonable defensive measure.

If you want richer diagnostics, you could also add exc_info=True to the main error log at Line 401, but the current behavior is already acceptable.


425-431: Final DB read is now guarded; consider handling a None project explicitly

Wrapping get_project(project_id) in a sqlite3.Error handler and returning a generic 500 avoids exposing DB internals, consistent with the rest of the flow. One small resilience improvement would be to guard against an unexpected None result (e.g., if the row were deleted between insert and read) instead of letting a TypeError bubble from the ProjectResponse constructor:

-    try:
-        project = app.state.db.get_project(project_id)
-    except sqlite3.Error as e:
-        logger.error(f"Database error retrieving project {project_id}: {str(e)}")
-        raise HTTPException(
-            status_code=500, detail="Database error occurred. Please try again later."
-        )
-
-    return ProjectResponse(
+    try:
+        project = app.state.db.get_project(project_id)
+    except sqlite3.Error as e:
+        logger.error(f"Database error retrieving project {project_id}: {e}", exc_info=True)
+        raise HTTPException(
+            status_code=500, detail="Database error occurred. Please try again later."
+        )
+
+    if not project:
+        # Extremely unlikely immediately after creation, but avoids obscure errors
+        raise HTTPException(
+            status_code=500,
+            detail="Project could not be retrieved after creation. Please try again later.",
+        )
+
+    return ProjectResponse(
         id=project["id"],
         name=project["name"],
         status=project.get("status", "init"),
         phase=project.get("phase", "discovery"),
         created_at=project["created_at"],
         config=project.get("config"),
     )

Not strictly required for correctness in the common path, but it makes the endpoint more robust to rare race conditions.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18adf2b and 46794cc.

📒 Files selected for processing (1)
  • codeframe/ui/server.py (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Use Python 3.11+ with async/await syntax, asyncio, and AsyncAnthropic for AI integrations in backend code
Lint code with ruff check . before committing; fix all style violations to maintain consistent code quality across the Python codebase
Use type hints with Python 3.11+ throughout all Python code; run type checking as part of quality gates to catch type errors before task completion

Files:

  • codeframe/ui/server.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T05:44:48.066Z
Learning: Applies to codeframe/persistence/database.py : Use SQLite with async support (aiosqlite) for all database operations; maintain schema tables: context_items (with agent_id scoping), blockers, code_reviews, token_usage, tasks, checkpoints
📚 Learning: 2025-12-05T05:44:48.066Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T05:44:48.066Z
Learning: Applies to codeframe/persistence/database.py : Use SQLite with async support (aiosqlite) for all database operations; maintain schema tables: context_items (with agent_id scoping), blockers, code_reviews, token_usage, tasks, checkpoints

Applied to files:

  • codeframe/ui/server.py
🧬 Code graph analysis (1)
codeframe/ui/server.py (1)
codeframe/persistence/database.py (5)
  • list_projects (1093-1117)
  • create_project (549-594)
  • update_project (1157-1189)
  • delete_project (1191-1199)
  • get_project (596-601)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Frontend Unit Tests
  • GitHub Check: Backend Unit Tests
  • GitHub Check: claude-review
🔇 Additional comments (2)
codeframe/ui/server.py (2)

16-16: Importing sqlite3 for explicit DB error handling is appropriate

Using sqlite3.Error as the common base for DB failures matches the current Database implementation and is a clean way to centralize DB-related exception handling.


369-393: Robust cleanup on workspace-path update failures

The nested try/except around update_project plus best-effort cleanup of both the DB row and workspace directory is well-structured and avoids leaving partially initialized projects behind. The final HTTP 500 response uses a generic message, so DB internals are not exposed.

No correctness issues spotted here; the control flow (log → cleanup → raise 500) is clear and safe.

@frankbria

Copy link
Copy Markdown
Owner Author

Response to Code Review Feedback

Thank you for the thorough reviews! I've addressed all the critical and recommended issues.


✅ First Review Issues (All Addressed)

Issue #1 - Missing Error Handling in Cleanup Path

  • Added try-except blocks around all delete_project() cleanup calls
  • Implemented best-effort cleanup with detailed logging
  • Prevents cleanup failures from masking original errors

Issue #3 - Missing Error Handling on update_project()

  • Wrapped update_project() in try-except for sqlite3.Error
  • Added comprehensive cleanup on database failure
  • Returns proper HTTP 500 response

Issue #4 - Test Coverage Gap

  • Added test_create_project_update_project_database_error
  • Added test_create_project_get_project_database_error
  • All 17/17 tests passing (100%)

Issue #6 - Error Message Exposure ✓ (see details below)


✅ Second Review Issues (All MUST FIX & SHOULD FIX Addressed)

Issue #1 - SECURITY: Information Disclosure Risk[MUST FIX]

Problem: Raw SQLite errors exposed database paths, schema, and internal details

Solution:

  • All database errors now return generic message: "Database error occurred. Please try again later."
  • Detailed errors logged server-side: logger.error(f"Database error: {str(e)}")
  • Applied to all 5 database operations:
    • list_projects()
    • create_project()
    • update_project()
    • delete_project()
    • get_project()

Before:

raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
# Exposed: "Database error: database is locked at /path/to/db"

After:

logger.error(f"Database error listing projects: {str(e)}")  # Server logs only
raise HTTPException(
    status_code=500, detail="Database error occurred. Please try again later."
)

Issue #2 - BUG: Incomplete Cleanup Path[SHOULD FIX]

Problem: Cleanup reconstructed path instead of using actual workspace_path variable

Solution:

  • Changed line 383-384 from reconstructing path to using actual variable
  • Ensures cleanup works even if path construction logic differs

Before:

workspace_dir = app.state.workspace_manager.workspace_root / str(project_id)  # Reconstructed
if workspace_dir.exists():

After:

if workspace_path.exists():  # Uses actual path from create_workspace() return value

Issue #3 - CODE QUALITY: Inconsistent Exception Handling[NICE TO HAVE]

Problem: Used broad Exception for filesystem operations vs specific sqlite3.Error for database

Solution:

  • Changed filesystem cleanup to use (OSError, PermissionError)
  • Consistent specificity across all error handling
  • Applied to both cleanup paths (lines 388, 413)

Before:

except Exception as cleanup_error:

After:

except (OSError, PermissionError) as cleanup_error:

Bonus Fix: HTTPException Re-raising

Problem: Database errors from update_project() were being caught by outer except Exception and re-wrapped as "Workspace creation failed"

Solution:

  • Added explicit except HTTPException clause before except Exception
  • Database errors now properly propagate with correct message
  • Test test_create_project_update_project_database_error now passes
except HTTPException:
    # Re-raise HTTPException from database error handling above
    raise

except Exception as e:
    # Handle workspace creation errors only
    ...

📊 Test Results

All 17/17 tests passing (100%):

  • ✅ 7 basic API tests
  • ✅ 3 integration tests
  • ✅ 7 error handling tests (including 2 new ones for update/get errors)

🔒 Security Improvements Summary

  1. No Information Disclosure: Generic error messages prevent leaking internal details
  2. Full Logging: Detailed errors preserved in server logs for debugging
  3. Production-Ready: Safe for deployment without exposing sensitive information
  4. Consistent Handling: All database operations follow same secure pattern

📝 Other Review Points

Issue #5 (Database Connection Context):
This is a broader architectural consideration beyond this PR's scope. The endpoint is marked async def but uses synchronous SQLite calls. Consider opening a separate issue to evaluate migrating to aiosqlite consistently across the codebase (as mentioned in CLAUDE.md).

Issues #4 & #5 from Second Review (Test Quality):
These are nice-to-have improvements that don't block merge:

  • Test assertions could be stricter (currently pass with "database" in message)
  • Missing edge case test for nested cleanup failures

I'm happy to address these in a follow-up PR if desired, but they're not critical for this security/reliability improvement.


Ready for merge! 🚀

All critical security issues addressed. Error handling is comprehensive, production-safe, and fully tested.

@frankbria
frankbria merged commit f3ec700 into main Dec 11, 2025
11 checks passed
@frankbria
frankbria deleted the claude/fix-db-error-handling-01TjfxMoUDF1mGkyob7b94Af branch December 11, 2025 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants