Skip to content

fix(followup-cadence): an empty tracker withholds cadenceDefaults, leaving the web cadence form blank #4005

Description

@rubicon

Summary

cadenceDefaults is a static constant (DEFAULT_CADENCE), but it is only emitted on the success path. analyzeFromContent() returns early when the tracker has no rows:

// followup-cadence.mjs:797
export function analyzeFromContent(trackerContent, followupsContent = '') {
  const apps = parseTrackerContent(trackerContent);
  if (apps.length === 0) {
    return { error: 'No applications found in tracker.' };
  }

That payload carries no cadenceDefaults, so a user with an empty tracker gets no cadence defaults even though the values do not depend on tracker data at all.

Reproduction

On main at 8a20e49, with a tracker created exactly as the onboarding Step 4 in AGENTS.md creates it (header row, no entries):

$ CAREER_OPS_ROOT=/tmp/new node followup-cadence.mjs --json
{
  "error": "No applications found in tracker."
}
$ echo $?
1

Positive control, same checkout, same command, one tracker row added:

$ CAREER_OPS_ROOT=/tmp/one-row node followup-cadence.mjs --json | grep -o cadenceDefaults
cadenceDefaults

Impact

readCoreDefaults() in web/src/app/api/followups/cadence/route.ts requires cadenceDefaults and returns null without it, so GET /api/followups/cadence responds with defaults: {} and effective: {} for a user who also has no followup_cadence: block in config/profile.yml.

cadence-settings.tsx maps effective onto its inputs and renders "" for any key the core did not supply, so all six fields on the Config page come up empty. Save is then rejected on the first empty field ("must be a whole number >= 0"), and nothing on the page states what the defaults are, so there is no value to type back in. The affected cohort is a fresh install: no applications tracked yet, no cadence overrides written yet.

The feature itself still behaves correctly, since resolveCadenceConfig() merges DEFAULT_CADENCE server-side. Only the settings form is affected.

Relationship to #3862

#3862 fixed the other half of this path by adding --json to KNOWN_FLAGS, and tests/web-core-argv-contract.test.mjs now pins the argv. That test covers which flags the core accepts, not what the payload contains, so this case is still open: the invocation now succeeds and the response is still missing the key the route needs.

Suggested fix

Include the defaults in the early return, since they are a constant and are already correct at that point:

return { error: 'No applications found in tracker.', cadenceDefaults: DEFAULT_CADENCE };

Additive, and consumers that branch on result.error are unaffected. Exit 1 should stay as it is: the analysis genuinely produced no entries, and the web route reads stdout rather than the exit code.

Adjacent observation

defaultsAvailable is emitted by the route but read by nothing in web/src. Its comment says it "tells the form to render its placeholders as unknown", and cadence-settings.tsx has no placeholder attribute on those inputs. Not part of this bug, but it means the intended honest-gap signal is not currently reaching the UI. Happy to split that out if it is worth tracking.

Environment

main at 8a20e49, package version 1.32.0.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions