---
title: task
type: task
status: planned
description: >-
  Extend the if (!leagueId) return guard, added to the draft sagas in fda51ff38 after signal 126301,
  to the remaining league-scoped sagas (rosters, trade, players, draft-pick-value) that read
  app.leagueId without it.
base_uri: user:task/league/guard-league-scoped-sagas-against-unresolved-league-id.md
created_at: '2026-08-21T22:39:25.286Z'
entity_id: 136093a5-1cd0-4ff1-b024-8a9525451cfe
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Low
public_read: true
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-10-11T21:15:08.121Z'
visibility_analyzed_at: '2026-11-06T00:17:55.603Z'
---

## Context

Signal 126301 (league-client log_error, `invalid input syntax for type integer: "undefined"` on
`/api/leagues/undefined/draft`) traced to `load_draft` in app/core/draft/sagas.js reading
`app.leagueId` without the `if (!leagueId) return` guard that `load_league`
(app/core/leagues/sagas.js:13), `initRosters` (app/core/rosters/sagas.js:76) and the teams sagas
already have. A deep link or stale navigation whose :lid is not a number left the league
unresolved and the API URL interpolated "undefined". The draft-family sagas were guarded in
commit fda51ff38 (deployed in bundle main.d47f6580.js).

The same unguarded pattern remains in every other league-scoped saga family: they read
`app.leagueId` from `get_app` and build a league-scoped URL with no falsy guard. Today
`app.leagueId` cannot be `undefined` (the app reducer normalizes to 0), so these would produce
`/api/leagues/0/...` rather than the invalid-integer 400. **This class is now LIVE, not latent
(2026-10-11):** a data-views page lands on an unresolved league id 0 and fires the unguarded
players saga, and `/api/leagues/0/players` 500s against the DB projection query — signal 137566
plus server mirrors 137567/137597, three hits from two users. The client guard is the primary
fix; the route's `// TODO verify leagueId` lets the bad id reach a 500 either way, so the
optional server-side lid validation matters more than when this was written.
The guard is one line and makes the whole client consistent.

## Call sites without the guard

- app/core/rosters/sagas.js — load_rosters, load_rosters_ignore_cache, load_rosters_for_year,
  updateRosterPlayerSlot, and the roster-actions block around lines 401-421
- app/core/trade/sagas.js — lines 88, 129, 135, 140, 146, 152, 165, 177
- app/core/players/sagas.js — lines 58, 72, 77
- app/core/draft-pick-value/sagas.js — line 10

`grep -rnE "leagueId \} = yield select\(get_app\)" app/core --include="*.js"` lists them.

## Fix pattern

Mirror the existing guard immediately after reading leagueId:

```js
const { leagueId } = yield select(get_app)
if (!leagueId) return
```

The guard only fires when the league is unresolved (0/undefined), which is never a valid target,
so user-initiated flows with a real league are unaffected.

## Plan

- [ ] Add the `if (!leagueId) return` guard to every unguarded leagueId read in
      app/core/rosters/sagas.js, app/core/trade/sagas.js, app/core/players/sagas.js and
      app/core/draft-pick-value/sagas.js
- [ ] `yarn exec eslint <files>` on each changed file
- [ ] Commit with an explicit pathspec; mark [skip ci] only if nothing tests the change
- [ ] Rebuild and redeploy the frontend bundle (yarn build && yarn deploy:dist &&
      yarn deploy:sourcemaps) per repository/active/league/docs/guides/ship.md, after
      origin/master has the commit
- [ ] (optional, needs a backend deploy window) validate lid as an integer in the
      /api/leagues/:lid/* routes so a bad lid 400s cleanly instead of surfacing Postgres
      syntax errors — deferred while backend deploys are gated by the unvalidated
      data-supplier checkpoint

## Key locations

- repository/active/league/app/core/ — the saga families
- repository/active/league/docs/guides/ship.md — deploy surface and verification
