Describe the Bug
The scheduler's polling fallback creates and commits a pending generation job, but then crashes while trying to launch it because the return
value from upsert_generation_job() is not assigned.
In packages/server/src/repowise/server/scheduler.py:137-166:
await crud.upsert_generation_job(
session,
repository_id=repo.id,
status="pending",
config={
"mode": "sync",
"trigger": "polling_fallback",
"before": stored_commit or "",
"after": current_head,
},
)
await session.commit()
task = asyncio.create_task(
execute_job(job.id, app_state),
name=f"poll-job-{job.id}",
)
job is undefined when execute_job(job.id, ...) is evaluated. This raises:
NameError: name 'job' is not defined
The exception is caught by the broad except Exception at the end of polling_fallback(), so the scheduler logs polling_fallback_failed, but the
job row has already been committed with status pending.
This is also an F821 undefined-name violation under the repository's Ruff configuration.
## Steps to Reproduce
1. Initialize and index a local repository.
2. Make another commit so the current Git HEAD differs from .repowise/state.json's last_sync_commit.
3. Start the server with repowise serve.
4. Wait for the polling fallback interval, which defaults to 15 minutes.
5. Inspect the server logs and generation jobs.
The same behavior can be reproduced faster in a test by obtaining the polling_fallback APScheduler job from setup_scheduler() and directly
awaiting its callback.
## Expected Behavior
When the stored commit differs from the repository's current HEAD, the polling fallback should:
1. Create a pending sync job.
2. Launch execute_job() using that job's ID.
3. Allow the job to transition from pending to running, then to a terminal state.
4. Update the repository's sync baseline after successful completion.
## Actual Behavior
The scheduler:
1. Creates and commits the pending job.
2. Logs polling_sync_enqueued.
3. Raises NameError because job is undefined.
4. Catches and logs the exception as polling_fallback_failed.
5. Leaves the job permanently in pending.
The pending row then causes the active-job guard to skip future polling attempts. Manual sync requests for the same repository can also
return:
409 A sync job is already in progress for this repository
The job remains stuck until the server restarts and startup cleanup marks interrupted pending/running jobs as failed.
## Environment
- Repowise version: current main / 0.31.0
- Python version: any supported version
- Installation method: source checkout
- Platform: platform-independent
## Proposed Fix
At minimum, retain the created job:
job = await crud.upsert_generation_job(...)
await session.commit()
task = asyncio.create_task(
execute_job(job.id, app_state),
name=f"poll-job-{job.id}",
)
Ideally, scheduler jobs should also use the same managed background-job launch path as REST-triggered jobs so that:
- the task is registered in app.state.job_tasks;
- cancellation works consistently;
- task-launch failures mark the database job as failed;
- unhandled task failures cannot leave the row pending or running.
## Suggested Regression Tests
Add scheduler tests covering:
- A diverged repository creates exactly one sync job.
- execute_job() receives the persisted job ID.
- The callback does not raise NameError.
- A task-launch failure moves the job to failed instead of leaving it pending.
- A second polling pass does not create a duplicate while a real job is active.
## Additional Context
The scheduler currently has no direct behavioral tests. Existing job-executor tests verify pipeline configuration, but they do not execute the
APScheduler polling callback.
Describe the Bug
The scheduler's polling fallback creates and commits a pending generation job, but then crashes while trying to launch it because the return
value from
upsert_generation_job()is not assigned.In
packages/server/src/repowise/server/scheduler.py:137-166: