Phase 7#12
Conversation
…erator, Resources, Solver, and Dashboard for enhanced user experience
… integrity - Implement `after_job_end` hook in ARQ worker to force-fail jobs on unhandled crashes, preventing infinite "processing" states. - Refactor resource upload logic to automatically delete storage files (DigitalOcean Spaces) if the database transaction fails, preventing "orphan" files. - Use explicit `_job_id` in ARQ to ensure reliable correlation between background tasks and database job records. - Add comprehensive logging to worker hooks for better production observability.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR extends job reliability through explicit job ID tracking and post-execution state reconciliation, adds comprehensive upload error handling with file cleanup, and introduces a premium monetization tier with upgrade prompts shown when users hit feature limits. Auth-gated public routes prevent logged-in users from accessing login/register pages, and a new welcome banner guides onboarding. ChangesJob Reliability, Premium Tiers, and Auth
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/app/routers/papers.py (1)
154-167:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMark
new_jobas failed when enqueue fails (don’t commit it as queued).At Line 163-166, enqueue failures still end in
await db.commit(), which can persistnew_jobinqueuedeven though no worker job exists.Suggested fix
+from datetime import datetime ... except Exception as e: logger.error(f"Redis enqueue error: {e}") + new_job.status = "failed" + new_job.completed_at = datetime.utcnow() new_paper.status = "failed" await db.commit() raise HTTPException(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/routers/papers.py` around lines 154 - 167, The enqueue exception block needs to mark the database job as failed instead of leaving it as queued: in the except handling for redis.enqueue_job failure, set new_job.status = "failed" (and optionally new_job.error or similar) before calling await db.commit(), ensure new_paper.status is also set to "failed" as already done, and then raise the HTTPException; this ensures the Job model (new_job) is persisted as failed rather than incorrectly remaining queued when redis.enqueue_job (generate_paper_task) fails.
🧹 Nitpick comments (1)
backend/app/workers/tasks.py (1)
21-22: ⚡ Quick winRemove commented-out debug code.
The commented
RuntimeErrorwas likely used to test the worker crash handling introduced in this PR. Debug artifacts like this should not be committed to the main branch, as they add noise and can confuse future developers.🧹 Proposed fix
print(f"\n🚀 [TASK START] Resource ID: {resource_id}") - - # raise RuntimeError("Simulated Worker Crash") async with SessionLocal() as db:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/workers/tasks.py` around lines 21 - 22, Remove the commented-out debug line "# raise RuntimeError("Simulated Worker Crash")" from backend/app/workers/tasks.py so no test/debug artifacts remain; locate the commented statement in the tasks module (the commented RuntimeError used to simulate a worker crash) and delete it, leaving the surrounding function/class logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/routers/resources.py`:
- Around line 123-127: The cleanup currently runs regardless of whether the DB
commit+refresh succeeded, which can delete the uploaded storage file while the
DB record exists; modify the handler around db.commit() and
db.refresh(new_resource) (the try/except/finally block that also calls the
storage deletion code) to track success: set a local flag (e.g.,
commit_and_refresh_ok = False), set it to True only after await db.commit() and
await db.refresh(new_resource) complete without error, and then in the
cleanup/finally section only delete the uploaded file(s) if
commit_and_refresh_ok is False (i.e., rollback or error path); keep existing
rollback and re-raise behavior intact so errors still propagate.
- Around line 139-142: The handler currently raises
HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed
to complete upload transaction: {str(e)}") which leaks internal exception text
(variable e) to clients; instead, log the full exception internally (e.g.,
logger.exception or logging.exception with the context in the same function
where the raise appears) and change the HTTPException detail to a generic
message like "Failed to complete upload transaction" or "Internal server error
while completing upload transaction" while preserving the 500 status; ensure you
reference the same raise HTTPException and status.HTTP_500_INTERNAL_SERVER_ERROR
locations and remove direct inclusion of str(e) from the client-facing detail.
- Around line 132-135: The except block uses an undefined name `logger`, causing
a NameError during cleanup; define a module logger and use it instead. Add an
import logging and create a logger via logger = logging.getLogger(__name__) at
module scope (or ensure an existing logger variable is initialized before the
except block), then keep the existing logger.error(...) and
storage_service.delete_file(object_name) calls in the except handler. Ensure the
symbol `logger` is available in the same module as the exception handler
(resources.py) so the cleanup path can log errors reliably.
In `@frontend/src/components/UpgradeModal.tsx`:
- Around line 67-76: The Button in UpgradeModal.tsx currently only logs and
calls onClose in its onClick handler; replace the console.log with a real
upgrade flow by invoking a dedicated upgrade function (e.g., a prop like
onUpgrade or a local helper such as initiateCheckout/initiateRazorpay) from the
Button's onClick, handle async errors and loading state (disable the Button
while pending), and only call onClose after a successful redirect/checkout
initiation; update the UpgradeModal component signature to accept an onUpgrade
prop if needed and use Button/onClick/onClose to trigger that flow.
In `@frontend/src/components/WelcomeBanner.tsx`:
- Around line 45-52: The dismiss control in WelcomeBanner.tsx is an icon-only
Button (component Button with onClick={handleDismiss} and child <X />) and needs
an accessible name; update the Button to provide an accessible label (e.g., add
aria-label="Dismiss" or aria-label="Close" or include a visually-hidden text
child) so screen readers can announce the action, ensuring the existing
handleDismiss and X icon usage remain unchanged.
---
Outside diff comments:
In `@backend/app/routers/papers.py`:
- Around line 154-167: The enqueue exception block needs to mark the database
job as failed instead of leaving it as queued: in the except handling for
redis.enqueue_job failure, set new_job.status = "failed" (and optionally
new_job.error or similar) before calling await db.commit(), ensure
new_paper.status is also set to "failed" as already done, and then raise the
HTTPException; this ensures the Job model (new_job) is persisted as failed
rather than incorrectly remaining queued when redis.enqueue_job
(generate_paper_task) fails.
---
Nitpick comments:
In `@backend/app/workers/tasks.py`:
- Around line 21-22: Remove the commented-out debug line "# raise
RuntimeError("Simulated Worker Crash")" from backend/app/workers/tasks.py so no
test/debug artifacts remain; locate the commented statement in the tasks module
(the commented RuntimeError used to simulate a worker crash) and delete it,
leaving the surrounding function/class logic unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 667c7351-0c8e-474a-b1a4-7a4533543fc9
📒 Files selected for processing (11)
backend/app/routers/papers.pybackend/app/routers/resources.pybackend/app/workers/arq_worker.pybackend/app/workers/tasks.pyfrontend/src/App.tsxfrontend/src/components/PublicRoute.tsxfrontend/src/components/UpgradeModal.tsxfrontend/src/components/WelcomeBanner.tsxfrontend/src/pages/Generator.tsxfrontend/src/pages/Resources.tsxfrontend/src/pages/Solver.tsx
| await db.commit() | ||
| await db.refresh(new_resource) | ||
| except Exception as e: | ||
| await db.rollback() | ||
| raise e # Caught by outer try/except |
There was a problem hiding this comment.
Don’t delete storage files after a successful DB commit.
If Line 123 commit succeeds but Line 124 refresh fails, current logic still hits cleanup at Line 135 and deletes the uploaded file, leaving a committed DB record pointing to missing storage.
Suggested fix
- try:
+ committed = False
+ try:
...
- await db.commit()
+ await db.commit()
+ committed = True
await db.refresh(new_resource)
...
except Exception as e:
# CLEANUP: Delete from storage if DB transaction failed
logger.error(f"Upload transaction failed, cleaning up storage: {e}")
- storage_service.delete_file(object_name)
+ if not committed:
+ storage_service.delete_file(object_name)Also applies to: 133-136
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/routers/resources.py` around lines 123 - 127, The cleanup
currently runs regardless of whether the DB commit+refresh succeeded, which can
delete the uploaded storage file while the DB record exists; modify the handler
around db.commit() and db.refresh(new_resource) (the try/except/finally block
that also calls the storage deletion code) to track success: set a local flag
(e.g., commit_and_refresh_ok = False), set it to True only after await
db.commit() and await db.refresh(new_resource) complete without error, and then
in the cleanup/finally section only delete the uploaded file(s) if
commit_and_refresh_ok is False (i.e., rollback or error path); keep existing
rollback and re-raise behavior intact so errors still propagate.
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail=f"Failed to queue background task: {str(e)}" | ||
| detail=f"Failed to complete upload transaction: {str(e)}" | ||
| ) |
There was a problem hiding this comment.
Avoid returning raw internal exception text to clients.
Line 141 exposes internal error details ({str(e)}) in a 500 response.
Suggested fix
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
- detail=f"Failed to complete upload transaction: {str(e)}"
+ detail="Failed to complete upload transaction."
)🧰 Tools
🪛 Ruff (0.15.13)
[warning] 139-142: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling
(B904)
[warning] 141-141: Use explicit conversion flag
Replace with conversion flag
(RUF010)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/routers/resources.py` around lines 139 - 142, The handler
currently raises
HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed
to complete upload transaction: {str(e)}") which leaks internal exception text
(variable e) to clients; instead, log the full exception internally (e.g.,
logger.exception or logging.exception with the context in the same function
where the raise appears) and change the HTTPException detail to a generic
message like "Failed to complete upload transaction" or "Internal server error
while completing upload transaction" while preserving the 500 status; ensure you
reference the same raise HTTPException and status.HTTP_500_INTERNAL_SERVER_ERROR
locations and remove direct inclusion of str(e) from the client-facing detail.
| <Button | ||
| className="w-full h-12 text-base font-bold shadow-lg shadow-primary/20" | ||
| onClick={() => { | ||
| // Future: Redirect to checkout or open Razorpay | ||
| console.log("Redirecting to payment..."); | ||
| onClose(); | ||
| }} | ||
| > | ||
| Upgrade Now — $9/mo | ||
| </Button> |
There was a problem hiding this comment.
Wire the primary CTA to an actual upgrade action.
Line 71 only logs and closes the dialog, so “Upgrade Now” does not upgrade or redirect users.
💡 Suggested fix
interface UpgradeModalProps {
isOpen: boolean;
onClose: () => void;
+ onUpgrade: () => void;
message?: string;
}
-export function UpgradeModal({ isOpen, onClose, message }: UpgradeModalProps) {
+export function UpgradeModal({ isOpen, onClose, onUpgrade, message }: UpgradeModalProps) {
@@
<Button
className="w-full h-12 text-base font-bold shadow-lg shadow-primary/20"
onClick={() => {
- // Future: Redirect to checkout or open Razorpay
- console.log("Redirecting to payment...");
- onClose();
+ onUpgrade();
}}
>
Upgrade Now — $9/mo
</Button>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/UpgradeModal.tsx` around lines 67 - 76, The Button in
UpgradeModal.tsx currently only logs and calls onClose in its onClick handler;
replace the console.log with a real upgrade flow by invoking a dedicated upgrade
function (e.g., a prop like onUpgrade or a local helper such as
initiateCheckout/initiateRazorpay) from the Button's onClick, handle async
errors and loading state (disable the Button while pending), and only call
onClose after a successful redirect/checkout initiation; update the UpgradeModal
component signature to accept an onUpgrade prop if needed and use
Button/onClick/onClose to trigger that flow.
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| onClick={handleDismiss} | ||
| className="size-8 text-muted-foreground hover:text-foreground" | ||
| > | ||
| <X className="size-4" /> | ||
| </Button> |
There was a problem hiding this comment.
Add an accessible label to the dismiss icon button.
The close control is icon-only, so assistive tech won’t get a clear action name.
♿ Suggested fix
<Button
variant="ghost"
size="icon"
onClick={handleDismiss}
+ aria-label="Dismiss welcome banner"
className="size-8 text-muted-foreground hover:text-foreground"
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={handleDismiss} | |
| className="size-8 text-muted-foreground hover:text-foreground" | |
| > | |
| <X className="size-4" /> | |
| </Button> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| onClick={handleDismiss} | |
| aria-label="Dismiss welcome banner" | |
| className="size-8 text-muted-foreground hover:text-foreground" | |
| > | |
| <X className="size-4" /> | |
| </Button> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/WelcomeBanner.tsx` around lines 45 - 52, The dismiss
control in WelcomeBanner.tsx is an icon-only Button (component Button with
onClick={handleDismiss} and child <X />) and needs an accessible name; update
the Button to provide an accessible label (e.g., add aria-label="Dismiss" or
aria-label="Close" or include a visually-hidden text child) so screen readers
can announce the action, ensuring the existing handleDismiss and X icon usage
remain unchanged.
after_job_endhook in ARQ worker to force-fail jobs on unhandled crashes, preventing infinite "processing" states.preventing "orphan" files.
_job_idin ARQ to ensure reliable correlation between background tasks and database job records.Summary by CodeRabbit
New Features
Bug Fixes