Skip to content

fix(executor): resolve {{@}} templates referencing run-code output (KEEP-442)#1147

Merged
eskp merged 2 commits intostagingfrom
feature/keep-442-http-request-template-substitution
May 6, 2026
Merged

fix(executor): resolve {{@}} templates referencing run-code output (KEEP-442)#1147
eskp merged 2 commits intostagingfrom
feature/keep-442-http-request-template-substitution

Conversation

@eskp
Copy link
Copy Markdown

@eskp eskp commented May 6, 2026

Summary

  • HTTP Request system action's endpoint, httpHeaders, and httpBody string fields silently resolved to "" when the template referenced a code/run-code upstream node, surfacing as HTTP request failed: URL is required at validation
  • Templates against HTTP and plugin-action upstreams worked because the resolver already unwraps {success, data, ...} shapes; code/run-code wraps user returns in {success, result, logs} and the resolver had no fallback for that
  • Fix is a 36-line additive change in lib/workflow/executor/executor.workflow.ts: extend resolveFromOutputData with a .result fallback that mirrors the existing .data fallback. Backward-compatible -- order is top-level -> .data -> .result, so every previously-resolving template still resolves to the same value

Linear: KEEP-442 -- unblocks dynamic Bridge Route Optimizer / MEV-Aware Swap Quote on the KEEP-430 catalog roadmap.

Repro (now passes)

trigger -> prep (code/run-code: return { url: "https://app.across.to/..." })
        -> across (HTTP Request, GET, endpoint = "{{@prep:Prep.url}}")

Pre-fix: across failed with URL is required (input.endpoint = "").
Post-fix: across returns a real Across API quote (verified end-to-end on local dev).

Test plan

  • pnpm test:unit tests/unit/http-request-template-substitution.test.ts -- 11 new tests, all pass; covers endpoint, httpHeaders, httpBody templating against run-code and HTTP upstreams plus precedence ordering
  • Full pnpm test:unit -- 3696 passed, 1 skipped (no regressions)
  • pnpm type-check -- clean
  • pnpm check -- no new lint issues in changed files
  • Local dev end-to-end: kh wf create + kh wf run against localhost:3000 returned a real Across quote

…EEP-442)

HTTP Request `endpoint` (and `httpHeaders`/`httpBody`) string fields
already pass through the workflow template substitution layer like
every other config string -- but `{{@prep:Prep.url}}` was resolving to
the empty string when `prep` was a `code/run-code` returning
`{ url: "..." }`. The bug shows up at HTTP request validation as
`URL is required`.

Root cause: `resolveFromOutputData` only unwraps the HTTP-style
`{ data: ... }` wrapper. `runCodeStep` returns `{ success, result, logs }`,
so when a downstream template references `Prep.url` (no explicit
`result.` prefix) the resolver finds neither `data.url` nor `data.data.url`
and falls back to the "missing path" branch, which substitutes "".

Fix: extend `resolveFromOutputData` with a `.result` fallback that
mirrors the existing `.data` fallback. Backward-compatible (`Prep.result.url`
still resolves directly via the top-level path) and also fixes any other
string field that references a code/run-code output's inner field
(protocol-action arg fields, downstream `code` strings, etc.).

Unblocks the dynamic Bridge Route Optimizer / MEV-Aware Swap Quote
workflows on the catalog roadmap.

- Add `hasNestedResultShape` helper alongside `hasNestedDataShape`
- Walk `.data` then `.result` so HTTP responses still take precedence
  when both wrappers are present
- Export `processTemplates` and `resolveFromOutputData` so the new
  unit test can drive the executor's substitution layer directly
- Add `tests/unit/http-request-template-substitution.test.ts` covering
  the bug repro plus header/body templating and ordering precedence

Verified end-to-end on local dev: trigger -> prep (code/run-code
returning {url}) -> across (HTTP GET with endpoint={{@prep:Prep.url}})
returned a real Across API response.
Address review feedback on PR #1147:

- Tighten `hasNestedDataShape` to also reject `data === null`, matching
  `hasNestedResultShape` -- removes the asymmetry the reviewer flagged
  and keeps the type guard honest (the runtime was already null-safe via
  resolveConfigFieldPath, but the predicate now matches behavior).
- Pin the intentional `.data` -> `.result` fall-through with an explicit
  test, so future readers know the behavior on outputs that carry both
  wrappers is by design.
- Document the existing limitation: a primitive `.result` (e.g. a
  code/run-code that returned a bare string) is not unwrapped because
  the fallback can only walk into objects. Whole-output references
  still resolve via top-level.
- Cover null-wrapper guards for both `.data` and `.result`.

Tests: 11 -> 15 cases, all pass. No code path changes beyond the
hasNestedDataShape predicate; existing 3696 unit tests still pass.
@eskp eskp merged commit b4e3a86 into staging May 6, 2026
35 checks passed
@eskp eskp deleted the feature/keep-442-http-request-template-substitution branch May 6, 2026 07:16
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

🧹 PR Environment Cleaned Up

The PR environment has been successfully deleted.

Deleted Resources:

  • Namespace: pr-1147
  • All Helm releases (Keeperhub, Scheduler, Event services)
  • PostgreSQL Database (including data)
  • LocalStack, Redis
  • All associated secrets and configs

All resources have been cleaned up and will no longer incur costs.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

ℹ️ No PR Environment to Clean Up

No PR environment was found for this PR. This is expected if:

  • The PR never had the deploy-pr-environment label
  • The environment was already cleaned up
  • The deployment never completed successfully

@eskp eskp mentioned this pull request May 6, 2026
4 tasks
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.

1 participant