Skip to content

[build-tools][steps] Load composite function catalogs for hook steps - #4063

Open
sswrk wants to merge 8 commits into
mainfrom
szymonswierk/eng-22387-build-tools-hook-composite-catalog
Open

[build-tools][steps] Load composite function catalogs for hook steps#4063
sswrk wants to merge 8 commits into
mainfrom
szymonswierk/eng-22387-build-tools-hook-composite-catalog

Conversation

@sswrk

@sswrk sswrk commented Jul 23, 2026

Copy link
Copy Markdown
Member

Why

Local composite functions can be referenced from hook steps (uses: ./...), but build-tools only builds the composite catalog from job steps.

How

  • Steps-based jobs: eager catalog loag from job steps, pass a lazy loadCompositeFunction loader so hook composites load only when their anchor runs
  • Native jobs: load only from wrapped anchors
  • Warn and skip for registered hook keys whose anchors never appear

Test Plan

Added unit tests.

@linear-code

linear-code Bot commented Jul 23, 2026

Copy link
Copy Markdown

ENG-22387

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.66%. Comparing base (4617db1) to head (060c136).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4063      +/-   ##
==========================================
+ Coverage   62.64%   62.66%   +0.03%     
==========================================
  Files         999      999              
  Lines       45175    45207      +32     
  Branches     9483     9488       +5     
==========================================
+ Hits        28294    28326      +32     
  Misses      15433    15433              
  Partials     1448     1448              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from b9e011c to eb60cb4 Compare July 23, 2026 16:17
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from eb60cb4 to e2453da Compare July 23, 2026 17:06
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from e2453da to be59908 Compare July 24, 2026 11:43
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from be59908 to e65600c Compare July 24, 2026 11:49
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch 2 times, most recently from 31f42c2 to b5884c9 Compare July 24, 2026 13:02
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from b5884c9 to e810dd3 Compare July 24, 2026 14:12
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from e810dd3 to 2f52da6 Compare July 24, 2026 14:41
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from 2f52da6 to 55401ab Compare July 24, 2026 15:00
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from 55401ab to 078f9c2 Compare July 24, 2026 15:17
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from ff09ed2 to c302845 Compare July 30, 2026 15:43
@sswrk
sswrk changed the base branch from szymonswierk/eng-22387-steps-composite-functions-in-hooks to graphite-base/4063 July 30, 2026 16:11
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from c302845 to ae26d93 Compare July 30, 2026 16:12
@graphite-app
graphite-app Bot changed the base branch from graphite-base/4063 to main July 30, 2026 16:12
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from ae26d93 to 400067c Compare July 30, 2026 16:12
@sswrk
sswrk marked this pull request as ready for review July 30, 2026 16:26
@sswrk
sswrk requested review from hSATAC and sjchmiela July 30, 2026 16:26

@hSATAC hSATAC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall this looks good to me, and the native/steps split makes sense. I left a couple of non-blocking inline comments on things I wasn’t sure were intentional.

One separate follow-up: eas workflow:validate only checks composites in job.steps, so references from hooks or defaults.hooks could pass validation and then fail on the worker. Not blocking this PR, but worth tracking.

Comment thread packages/build-tools/src/steps/compositeFunctions.ts Outdated
Comment thread packages/build-tools/src/steps/compositeFunctions.ts Outdated
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from 400067c to f940337 Compare July 31, 2026 10:06
@sswrk

sswrk commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

One separate follow-up: eas workflow:validate only checks composites in job.steps, so references from hooks or defaults.hooks could pass validation and then fail on the worker. Not blocking this PR, but worth tracking.

Thanks for thinking about everything. This is coming in the next PR, I'm also looking into reducing the duplication between build-tools and eas-cli.

@sswrk sswrk changed the title [build-tools] Load composite function catalogs for hook steps [build-tools][steps] Load composite function catalogs for hook steps Jul 31, 2026
@sswrk
sswrk requested a review from hSATAC July 31, 2026 13:37
@sswrk
sswrk force-pushed the szymonswierk/eng-22387-build-tools-hook-composite-catalog branch from 8fa56f7 to d156a2e Compare July 31, 2026 14:46

@hSATAC hSATAC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The lazy loader addresses what I raised last time, and the rejectingLoader() test covers it. Removing hooks from buildCompositeFunctionCatalogAsync also resolves my other comment.

I left one blocking comment in jobHooks.ts about the hook error messages regressing from main. The other three are non-blocking.

Comment on lines +96 to +116
const catalogRootSteps = wrappedAnchors.flatMap(anchor =>
(['before', 'after'] as const).flatMap(side => {
const steps = hooks[`${side}_${anchor}`];
return Array.isArray(steps) ? steps : [];
})
);
let compositeFunctionCatalog: CompositeFunctionCatalog;
try {
compositeFunctionCatalog = await buildCompositeFunctionCatalogAsync(
ctx.getReactNativeProjectDirectory(),
{ steps: catalogRootSteps, logger: ctx.logger }
);
} catch (err) {
throw new UserError(
ErrorCode.HOOKS_ERROR,
`Failed to load a local composite function referenced from the job's hooks: ${
err instanceof Error ? err.message : String(err)
}`,
{ cause: err }
);
}

@hSATAC hSATAC Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This changes hook error reporting on the native path, and I think it’s a regression from main.

On main, a bad composite under a wrapped key is handled by constructHookEntriesAsync, so the error includes the hook key:

Failed to parse hooks.before_install_node_modules: Local composite function "./.eas/functions/missing" does not exist. Expected a "function.yml" (or "function.yaml") file at ...

Here, the catalog build gets there first after catalogRootSteps has flattened the wrapped anchors, so that context is lost:

Failed to load a local composite function referenced from the job's hooks: Local composite function "./.eas/functions/missing" was referenced by a step but no such composite function exists. ...

working_directory makes this more noticeable because it isn’t a load failure at all. collectLocalCompositeFunctionPathsFromSteps throws while collecting paths, but the error still gets the Failed to load prefix:

  • main: Failed to parse hooks.before_install_node_modules: "working_directory" is not supported ...
  • here: Failed to load a local composite function referenced from the job's hooks: "working_directory" is not supported ...

The steps path already avoids this by rethrowing BuildConfigError unchanged. Would it be better to build the catalog per key inside the for (const anchor of wrappedAnchors) loop below? That would keep the key in scope and let BuildConfigError fall through to the existing Failed to parse hooks.${key} wrapper.

The new test only checks the inner no such composite function exists, so it passes with either behavior.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair point about the regression. Applied a suggestion and added a test to assert the expected fomrat of the error message: dbb877b

Comment thread packages/steps/src/StepsConfigParser.ts Outdated
}
}

for (const hookKey of Object.keys(validatedHooks)) {

@hSATAC hSATAC Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: this warning can’t be reached for one of the cases it covers.

validateAllStepFunctionsExist runs over every registered hook key, even when its anchor isn’t present. Local composites are skipped and handled by the lazy loader, but a plain uses: still throws before reaching this warning.

For example:

defaults:
  hooks:
    before_submit:
      - uses: eas/some_newer_function

jobs:
  mobile_build:
    type: build
  lint:
    type: custom
    steps:
      - run: yarn lint

lint has no submit anchor, so I’d expect only the warning. Instead it fails with Calling non-existent functions: "eas/some_newer_function".

jobHooks.ts deliberately doesn’t validate function existence for unwrapped keys, so native and steps jobs behave differently here. Would it make sense to move this check into constructHookSideEntriesAsync, where it would only run for anchors that actually appear? constructHookEntriesAsync already validates per key that way.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Makes sense, suggestion applied: 7e12389

Comment thread packages/steps/src/StepsConfigParser.ts Outdated

for (const hookKey of Object.keys(validatedHooks)) {
const parsed = parseHookKey(hookKey);
if (parsed !== null && !seenAnchorIds.has(parsed.anchorId)) {

@hSATAC hSATAC Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor and non-blocking: parsed can’t be null here. validateHooks() only keeps keys for which parseHookKey() returned non-null, so this is narrowing rather than a real check.

Would it be better for validateHooks() to return the anchor IDs it already parsed, or type its result as Partial<Record<HookKey, Step[]>>? Either would avoid parsing the keys again here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Cleaned this up 083c0ba

Comment thread packages/steps/src/StepsConfigParser.ts Outdated
const parsed = parseHookKey(hookKey);
if (parsed !== null && !seenAnchorIds.has(parsed.anchorId)) {
this.ctx.baseLogger.warn(
`Ignoring "hooks.${hookKey}": this build does not run the "${parsed.anchorId}" step.`

@hSATAC hSATAC Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor and non-blocking: jobHooks.ts warns for both an unknown hook key and a registered key whose anchor isn’t wrapped. This adds the second warning with matching wording.

Should validateHooks() also warn about the unknown keys it skips? parseHookKey(hookKey) === null currently just continues, so a typo like before_install_node_module is silently dropped for a steps job but reported for a native one.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added the warning 0593495

@sswrk sswrk added the ai-review Commits pushed to PRs with this label be automatically reviewed. label Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

🤖 AI code review

Decision: Approve with comments

One warning remains in packages/steps/src/StepsConfigParser.ts. The code logs an untrusted hook key without removing terminal control characters.

🟡 Warning (1)

  • Unknown hook key can inject terminal control characterspackages/steps/src/StepsConfigParser.ts:216 (security) · id:cdc3a52d7fd0
    The hook key comes from workflow data. The warning writes it to the build log without removing control characters. An attacker can put ANSI escape codes in the key and spoof CI output.
    Suggestion: Remove ANSI escape codes and control characters from hookKey before logging it.

This review is advisory — it never blocks a merge and never auto-approves.

@github-actions

Copy link
Copy Markdown

✅ Thank you for adding the changelog entry!

@sswrk

sswrk commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

Re. AI review:

Hook composite errors lose the hook key

Fair, goes hand in hand with #4063 (comment). Addressed: 060c136

Unknown hook key can spoof terminal output

Leaving this out for now, I think it's not really specific to this PR

@sswrk
sswrk requested a review from hSATAC July 31, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Commits pushed to PRs with this label be automatically reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants