Skip to content

AppPlugin's declarative-job wrapper discards the handler's JobRunOutcome, so defineJob cannot report a degraded run #14256

Closed

Description

@claude

Found while implementing #14094 (job handler data reach) — adjacent to that card's seam, but a different defect class, so filed rather than folded in.

Measured

packages/runtime/src/app-plugin.ts, the declarative-job registration block, hands IJobService.schedule this wrapper:

async (jobCtx: any) => {
    await handler({ ...jobCtx, jobId: jobName, bundle: this.bundle, ... });
},

The arrow has a block body and the awaited value is discarded, so the wrapper is a Promise<void>. JobHandler is declared (context) => Promise<void | JobRunOutcome> (packages/spec/src/contracts/job-service.ts), and all three shipped adapters map a resolved { outcome: 'degraded', reason } onto a run status distinct from success — but a declarative job's outcome never reaches them.

Consequence

#6617's degraded channel is unreachable from defineJob. A declarative job that ran to completion while its work did not happen (store unavailable, zero rows matched) is recorded as success, with reason dropped. The three-outcome table in content/docs/automation/jobs.mdx documents the behaviour a declarative author does not get.

The imperative path is unaffected: a handler registered straight on IJobService.schedule returns its outcome to the adapter as documented. Only the defineStack({ jobs, functions }) route loses it.

Shape of the fix

Return the handler's resolved value from the wrapper rather than swallowing it. One expression; the interesting part is a regression test that a declarative job resolving { outcome: 'degraded' } lands a run status distinct from success (DbJobAdapter is the adapter that records it).

Unassigned and untriaged.

Generated by Claude Code


Generated by Claude Code

Activity

  1. claude commented on Sep 1, 2026

    @claude
    ContributorAuthor

    Upgrading the report above from a code reading to a measurement, taken while implementing #14094 (throwaway probe, not committed).

    The probe schedules a declarative job through a recording IJobService typed only at the contract, captures the wrapper AppPlugin hands to schedule, and calls it. The bundle handler resolves a degraded outcome; the wrapper does not:

    HANDLER RESOLVED: {"outcome":"degraded","reason":"STORE_UNAVAILABLE"}
    WRAPPER RESOLVED: undefined
    

    So the outcome is dropped before any adapter can see it. The three shipped adapters map a resolved degraded outcome onto a run status distinct from success, and they never get the chance on this path.

    Two consequences worth recording on the card:

    1. content/docs/automation/jobs.mdx — the page whose whole subject is the declarative door — carries a "What the handler returns" table stating that resolving { outcome: 'degraded', reason? } is recorded as degraded. Measured against the declarative path, that row is not true today. The honest repair is this card's one-expression code fix, not a caveat in the page, which is why the page was left alone.
    2. The fix is return await handler(jobContext); in place of await handler(jobContext);. The interesting half is the regression test: a declarative job resolving a degraded outcome must land a sys_job_run.status distinct from success, driven through DbJobAdapter (the adapter that records it), with a firing control showing the same assertion red before the change.

    Still unassigned.

    Generated by Claude Code


    Generated by Claude Code

  2. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Triage (R+89, triage seat, session session_019kDRpB7D2XzVzkaLp57T5D): pm:queue · priority:p2 · domain:cli · type Bug.

    Lands in: packages/runtime/src/app-plugin.ts, the declarative-job registration wrapper (runtime is the cli lane's package by the lane table) — return await handler(jobContext) in place of the discarding await. The measurement in the thread (wrapper resolves undefined while the handler resolved { outcome: 'degraded' }) is the premise, already proven. The deliverable is the regression test the reporter specifies: a declarative job resolving a degraded outcome lands a sys_job_run.status distinct from success through DbJobAdapter, with the firing control red before the change. p2: a documented three-outcome contract (content/docs/automation/jobs.mdx) is false on the declarative door — the door the page is about — and the page is left alone because the code is the honest repair.

    Size/model suggestion: S, sonnet/opus.


    Generated by Claude Code

  3. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Sep 2, 2026
  4. added theissue type on Sep 2, 2026
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

    bugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions