Skip to content

feat: task retry re-resolves against current configuration (ADR-0014) - #9

Merged
hutusi merged 8 commits into
mainfrom
feat/retry-re-resolution
Jul 27, 2026
Merged

hutusi merged 8 commits into
mainfrom
feat/retry-re-resolution

Conversation

@hutusi

@hutusi hutusi commented Jul 27, 2026 •

Copy link
Copy Markdown
Contributor

Closes #7.

Why

Task retry copied the previous run's frozen resolution verbatim ("pinned — retries never re-resolve"), which made it structurally unable to heal configuration failures. The motivating incident: a requirement-delivery run whose reviewer slot froze gpt-5.1-codex — a model id ChatGPT-account Codex auth rejects with a 400. After fixing the grants, the natural next step (Retry) would have re-billed the entire pipeline from checkout — clarify loop, plan approval, full implementation — and died at review identically. The only working recovery was re-filling the submission form.

Two facts settled the design (recorded in ADR-0014): resolution-shaped failures are not classifiable from runs.error.code (the incident finalized as catch-all model_error; executor_unavailable never reaches a run's error), and resolveAgentBindings is deterministic — with unchanged configuration, re-resolution reproduces the previous bindings exactly, so it strictly generalizes copying. Transient-failure retries therefore behave as before; behavior changes only when configuration changed, which is precisely when the operator wants it to.

What

One commit per concern:

  1. docs(adr) — ADR-0014. Retry is a re-submission of the pinned task: template version + params snapshot stay pinned; agent bindings, model resolution, resource manifest, budget, quota headroom, and repo-ref ownership re-derive against current configuration. Records the rejected alternatives (status quo, two-button UX, failure-code classification, override-reconstruction heuristics).
  2. feat(db) — persist submit-time agent overrides. The submit form's agents slot overrides were consumed at resolution and stored nowhere, so a re-resolving retry couldn't distinguish a deliberate executor choice from a template default. They now land on tasks.agent_overrides (migration 0008) and re-apply at retry; pre-migration tasks retry with template defaults (recorded consequence).
  3. feat(api) — the re-resolution. A resolveRunPlan helper shared by submit and retry (the two paths cannot drift). Retry gains full submit parity it silently lacked: quota hard-stop, repo-ref ownership, refreshed manifest/budget, transactional insert (run + latestRunId together), audit payload naming the source run. assertResolutionCredentialed (ADR-0013 amendment 1) is retired — full resolution enforces the same credential gate and every stronger one. Six submit error codes retry can now surface (executor_unavailable and siblings) join both locales' error catalogs.
  4. feat(web) — honest Retry. A confirm dialog states the new run starts over from checkout under today's configuration and re-asks earlier answers/approvals; rejections surface as localized toasts; run-page actions are hidden from viewers (member-gated endpoints — they could only collect a 403).

Verification

  • bun run check, bun test (250 pass / 0 fail, integration suites ran against Postgres — none skipped), bun run templates:validate, bun run build, bunx commitlint --from main --to HEAD — all green.
  • New integration tests: adding a dashscope credential flips the retried run's resolved provider while templateVersionId/paramsSnapshot provably stay pinned; a submit-time reviewer override survives retry; empty grants → 400 skill_not_granted; exhausted quota → 400 quota_exhausted. The pre-existing credential-gate retry test passes unchanged through the new path.
  • Docs updated per the docs map: design 01/04/05/06, both manual locales (02/03), CHANGELOG under [Unreleased].

Summary by CodeRabbit

  • New Features
    • Retry now re-submits by creating a fresh run using the pinned task version/parameters while re-applying the latest project configuration (including resource, agent/model bindings, and budget/quota headroom).
    • Submit-time agent selections/overrides are preserved across retries.
  • Bug Fixes
    • Retry fails fast with submit-time error codes when current configuration can’t satisfy grants/credentials, quota, skill versions, disabled registries/MCP servers, or missing/disabled fabers; concurrent retries are serialized (run_active).
  • User Experience
    • Added a retry confirmation warning that execution restarts from checkout and repeats earlier prompts.
    • Viewers no longer see retry action controls.
    • Retry errors now show clear localized messages.

hutusi added 4 commits July 27, 2026 21:19
A live incident showed task retry is a trap for configuration failures:
it copies the frozen model resolution verbatim, so after fixing grants
the retried run re-bills the whole pipeline from checkout and fails at
the same step. ADR-0014 decides that retry becomes a re-submission of
the pinned task — template version and params stay pinned, everything
configuration-derived (bindings, models, manifest, budget, quota, repo
refs) re-resolves — because re-resolution is deterministic when config
is unchanged and auto-classification of resolution-shaped failures is
impossible (the incident finalized as catch-all model_error). Also
decides persisting submit-time agent overrides, deleting the subsumed
assertResolutionCredentialed gate, and the UI confirmation.
The agents overrides a user picks on the submit form were consumed at
resolution and stored nowhere, so nothing downstream could distinguish
a deliberate slot choice from a template default that happened to
resolve the same way. A re-resolving retry (ADR-0014) needs the raw
override object back, so it now lands on tasks.agent_overrides
(migration 0008, default {}). Tasks created before the column exists
retry with template defaults — recorded as an ADR consequence.
Retry copied the previous run's frozen resolution verbatim, which made
it structurally unable to heal configuration failures: after fixing
grants/registry/credentials the retried run re-billed the whole
pipeline from checkout and died at the same step. Per ADR-0014 retry is
now a re-submission of the pinned task — template version and params
snapshot stay pinned, everything configuration-derived re-resolves
through resolveRunPlan, a helper shared with submit so the two paths
cannot drift. With unchanged configuration re-resolution is
deterministic, so transient-failure retries behave exactly as before.

Retry also gains submit parity it silently lacked: the quota hard-stop,
repo-ref ownership check, a transactional insert (run + latestRunId
together), and an audit payload naming the source run. The narrow
assertResolutionCredentialed gate (ADR-0013 amendment 1) is retired —
full resolution enforces the same credential check and every stronger
one. Submit error codes retry can now surface (executor_unavailable
and siblings) join both locales' error catalogs.
Retry now re-resolves against current configuration (ADR-0014), which
gives the button two properties worth stating before the click: the new
run starts over from checkout — previously answered questions and
approved plans are asked again — and it binds agents/models to today's
project configuration. A ConfirmDialog says both. Re-resolution can
also reject the way submit does (grants, credentials, quota), so the
mutation gains a localized error toast instead of failing silently.
The run-page actions are additionally hidden from viewers, who could
only ever collect a 403 from the member-gated endpoints.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@hutusi, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 66560ccc-d3ac-4b9b-b5ad-e6c55d719031

📥 Commits

Reviewing files that changed from the base of the PR and between 246fd50 and 3067100.

📒 Files selected for processing (1)
  • apps/api/src/test/execution.integration.test.ts
📝 Walkthrough

Walkthrough

Task retries preserve the pinned template and parameters while re-resolving agents, models, resources, budgets, credentials, and grants from current configuration. Submit-time overrides persist across retries, configuration errors fail fast, and the UI adds confirmation, localized errors, and viewer restrictions.

Changes

Retry re-resolution

Layer / File(s) Summary
Resolution contracts and resource validation
packages/orchestration/src/resolve.ts, apps/worker/src/deps/resources.ts
Resolution validates active Fabers, executors, and skill versions; shared helpers are used for authorization and worker materialization.
Run-plan persistence and submit integration
apps/api/src/routes/execution.ts, packages/db/src/schema/runs.ts, packages/db/drizzle/*
A shared resolver derives current resources, bindings, and budgets; submit-time agent overrides are persisted in tasks.agent_overrides.
Retry execution and validation
apps/api/src/routes/execution.ts, apps/api/src/test/*
Retry re-resolves current configuration against pinned inputs, rejects unavailable resources, serializes concurrent retries, and records audits.
Retry controls and localized feedback
apps/web/src/pages/RunDetailPage.tsx, packages/i18n/locales/*
Retry requires confirmation, displays API errors in a toast, hides actions from viewers, and adds English and Chinese strings.
Retry behavior documentation
docs/adr/*, docs/design/*, docs/manual/*, CHANGELOG.md, ARCHITECTURE.md
Documentation describes pinned inputs, current-configuration re-resolution, checkout restart behavior, failure codes, and confirmation UX.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant RunDetailPage
  participant RetryEndpoint
  participant resolveRunPlan
  participant Database
  User->>RunDetailPage: Confirm retry
  RunDetailPage->>RetryEndpoint: POST /tasks/:id/retry
  RetryEndpoint->>Database: Load pinned template, params, and overrides
  RetryEndpoint->>resolveRunPlan: Resolve current configuration
  resolveRunPlan-->>RetryEndpoint: Return run plan or validation error
  RetryEndpoint->>Database: Create run and audit
  RetryEndpoint-->>RunDetailPage: Return new run or error
Loading

Possibly related issues

  • ainaive/agrippa issue 7: Covers retry re-resolution against current configuration while preserving pinned inputs.

Possibly related PRs

  • ainaive/agrippa#2: Introduces authorization and resource-pinning primitives reused by the retry resolution path.
  • ainaive/agrippa#6: Both changes modify provider credential-aware resolution and gating behavior.

Suggested reviewers: leonaltair

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: task retry now re-resolves against the current configuration under ADR-0014.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/retry-re-resolution

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 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 `@apps/api/src/routes/execution.ts`:
- Around line 499-520: Update the retry creation transaction around the runs
insert to serialize retries per task: lock the task row, then re-read the
current latest run within the same transaction and validate it remains eligible
before deriving the next run number. Use that transaction-local latest run for
the inserted run’s number and copied fields, preventing concurrent retries from
relying on the stale outer latest value.

In `@apps/api/src/test/execution.integration.test.ts`:
- Around line 465-466: In the retry test beginning with “retry preserves the
user's submit-time agent overrides,” remove the duplicate consecutive const
types declaration, keeping one declaration for the test’s existing setup.

In `@apps/web/src/pages/RunDetailPage.tsx`:
- Around line 105-108: Update the retry mutation’s onError handler in
RunDetailPage to use the existing toastApiError helper instead of duplicating
ApiError-versus-string handling, preserving the current error toast behavior and
centralized errors-catalog lookup.

In `@CHANGELOG.md`:
- Around line 67-71: Update the older provider-credentials changelog entry to
remove its claim that retries re-assert credentials against a frozen resolution.
Align its wording with ADR-0014 by describing retry as using the current
configuration and full re-resolution, while preserving the entry’s unrelated
historical details.
🪄 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 Plus

Run ID: 18adba82-fb08-4531-9962-03f71ac516ee

📥 Commits

Reviewing files that changed from the base of the PR and between 8ac1a10 and 6194280.

📒 Files selected for processing (23)
  • CHANGELOG.md
  • apps/api/src/routes/execution.ts
  • apps/api/src/test/execution.integration.test.ts
  • apps/api/src/test/providers.integration.test.ts
  • apps/web/src/pages/RunDetailPage.tsx
  • docs/adr/0014-retry-re-resolves-configuration.md
  • docs/design/01-domain-model.md
  • docs/design/04-execution-runtime.md
  • docs/design/05-api-and-auth.md
  • docs/design/06-frontend.md
  • docs/manual/en/02-concepts.md
  • docs/manual/en/03-running-tasks.md
  • docs/manual/zh-CN/02-concepts.md
  • docs/manual/zh-CN/03-running-tasks.md
  • packages/db/drizzle/0008_agent-overrides.sql
  • packages/db/drizzle/meta/0008_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema/runs.ts
  • packages/i18n/locales/en/errors.json
  • packages/i18n/locales/en/runs.json
  • packages/i18n/locales/zh-CN/errors.json
  • packages/i18n/locales/zh-CN/runs.json
  • packages/orchestration/src/resolve.ts

Comment thread apps/api/src/routes/execution.ts
Comment on lines +465 to +466
it("retry preserves the user's submit-time agent overrides", async () => {
const types = await jsonOf<Array<{ id: string; slug: string }>>(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate declaration.

Line 466 declares const types twice consecutively, making this test file fail to parse/type-check.

🤖 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 `@apps/api/src/test/execution.integration.test.ts` around lines 465 - 466, In
the retry test beginning with “retry preserves the user's submit-time agent
overrides,” remove the duplicate consecutive const types declaration, keeping
one declaration for the test’s existing setup.

Comment thread apps/web/src/pages/RunDetailPage.tsx Outdated
Comment thread CHANGELOG.md
hutusi added 3 commits July 27, 2026 22:09
Four P2 findings, all confirmed against the code:

- Disabled registry rows resolved as live: the MCP slug map included
  disabled servers, skill authorization never verified an active
  skill_versions row satisfies the ref's semver range, and the
  v1-sentinel/demo faber path accepted taskType.defaultFaberId without
  the active-row check the explicit paths had. All three now fail
  resolution (new skill_version_unavailable code, both locales); the
  version-matching rule is one shared helper (pickActiveSkillVersion)
  used by submit-time authorization and the worker's materializer so
  the two cannot drift.
- Concurrent retries raced: the latest-run read preceded the
  transaction, so two racers both computed number N+1 and the loser
  died on runs_task_number_uq as a 500. The number computation and
  terminal gate now run under SELECT ... FOR UPDATE of the task row;
  the second racer gets the intended 409 run_active.
- Audit rows were written after their transaction committed, in retry
  and submit alike — an audit failure left a committed mutation
  unaudited, violating the invariant ADR-0013 amendment 1 documents.
  Both audits now ride the mutation's transaction.

Minors: ARCHITECTURE.md ADR count 13 -> 14; unused
isCredentialGatedExecutor import dropped. Recorded as an ADR-0014
amendment per the 0013 review-round precedent.
…t the MCP filter

repo_not_in_project was thrown by verifyRepoRefs but absent from both
error catalogs — reachable at submit before, but the retry toast made
it the first raw-English SubmitError a zh-CN user would actually see.
Both locales gain the key, and a regression test drives the retry path
against a deleted repo connection.

The round-1 disabled-MCP filter also ships its missing regression test:
bug-localize-fix references MCP 'github' optionally, so the test proves
an active granted server is pinned into the retried run's manifest and
a disabled one silently drops — the observable optional-ref semantics
of the active-only slug map. Both admin manuals (en + zh-CN) document
the skill-version and disabled-MCP resolution rules.
CodeRabbit's pass over the original push: its retry-serialization
finding was already fixed by the codex round-1 commit, and the
duplicate-declaration report is a false positive (two 'const types'
in different function scopes; the file type-checks and CI was green
on the reviewed head). The two remaining nits are real: the retry
mutation's onError duplicated the ApiError-vs-string pattern that
lib/toast.ts centralizes, and the provider-credentials changelog
entry still described the retired frozen-resolution re-check that
ADR-0014 superseded.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@apps/api/src/test/execution.integration.test.ts`:
- Around line 524-550: Update the “retry rejects a default faber that was
disabled since submit” and “retry rejects a required skill whose versions were
all deprecated” tests to explicitly mark the task’s existing/latest run as
terminal before invoking the retry endpoint. Reuse the file’s established
run-status setup from the other retry tests, preserving the expected
faber_unknown and skill_version_unavailable responses without relying on test
order.
🪄 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 Plus

Run ID: 0c69ead2-6ae2-4f2e-9f64-e2c8a3a1eee3

📥 Commits

Reviewing files that changed from the base of the PR and between 6194280 and 246fd50.

📒 Files selected for processing (12)
  • ARCHITECTURE.md
  • CHANGELOG.md
  • apps/api/src/routes/execution.ts
  • apps/api/src/test/execution.integration.test.ts
  • apps/web/src/pages/RunDetailPage.tsx
  • apps/worker/src/deps/resources.ts
  • docs/adr/0014-retry-re-resolves-configuration.md
  • docs/manual/en/04-administration.md
  • docs/manual/zh-CN/04-administration.md
  • packages/i18n/locales/en/errors.json
  • packages/i18n/locales/zh-CN/errors.json
  • packages/orchestration/src/resolve.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/src/pages/RunDetailPage.tsx
  • docs/adr/0014-retry-re-resolves-configuration.md
  • packages/i18n/locales/zh-CN/errors.json
  • apps/api/src/routes/execution.ts

Comment thread apps/api/src/test/execution.integration.test.ts
The two rejection tests relied on a preceding test leaving the task's
latest run terminal — reordering the file would turn their expected
400s into 409 run_active (the guard this same PR adds). They now force
the terminal state themselves like every other retry test in the file
(CodeRabbit round 2).

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@hutusi
hutusi merged commit 1b41795 into main Jul 27, 2026
6 of 7 checks passed
@hutusi
hutusi deleted the feat/retry-re-resolution branch July 27, 2026 22:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Task retry pins the frozen model resolution — configuration-level failures can never be healed by retrying

1 participant