Skip to content

feat(cloud): send workflowPath so the launcher can skip the $HOME upload#766

Merged
khaliqgant merged 3 commits intomainfrom
fix/send-workflow-path
Apr 22, 2026
Merged

feat(cloud): send workflowPath so the launcher can skip the $HOME upload#766
khaliqgant merged 3 commits intomainfrom
fix/send-workflow-path

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Apr 22, 2026

Summary

Companion to AgentWorkforce/cloud#276. When a user runs
agent-relay cloud run <file> --sync-code, send the workflow's path
relative to the tarball root in the run request body.

The cloud launcher uses that path to set WORKFLOW_FILE directly at
/project/<path> and skip uploading the file to $HOME — which means
sibling-relative imports like ../shared/models.ts resolve against the
repo layout instead of $HOME (where they currently break).

Why

Today, the launcher uploads the workflow source to $HOME/workflow.ts, discarding the repo-relative path. When bun runs the file there, ../shared/models.ts resolves against $HOME instead of the repo. The in-sandbox rediscovery matcher that tries to reverse-engineer the path via sha256 fails silently on the first byte-level mismatch (line endings, tarball normalization). See cloud#276 for the full write-up.

Preserving the path end-to-end removes the rediscovery guesswork entirely.

Change

  • packages/cloud/src/workflows.ts:
    • New exported helper relativizeWorkflowPath(workflowArg) — normalizes to a forward-slash relative path against process.cwd() (the tarball root), returns null for absolute-outside-cwd or ..-escaping paths.
    • In the runWorkflow sync-code branch, compute the relative path and set requestBody.workflowPath when it's safe. Server falls back to the legacy $HOME upload path when absent.
  • packages/cloud/src/workflows.test.ts — 6 cases covering sibling paths, leading ./, absolute-inside-cwd, absolute-outside-cwd, ..-escape, and the edge case of cwd itself.

Rollout

  1. Merge + publish as agent-relay@4.0.37.
  2. Users on the new CLI → workflowPath gets sent → cloud uses it (requires cloud#276 in prod).
  3. Users on older CLIs → no field → cloud falls back to current $HOME upload behaviour (still works, just without sibling-relative import support — same as today).

Test plan

  • npx tsx --test packages/cloud/src/workflows.test.ts — 6/6 pass.
  • TypeScript compilation clean.
  • After both this and cloud#276 ship, re-run NightCTO's failing workflow: agent-relay cloud run workflows/security-runtime/01-broker-runtime-execution.ts --sync-code. Should succeed end-to-end with no rediscovery-matcher warn log.

🤖 Generated with Claude Code


Open in Devin Review

Pair with AgentWorkforce/cloud#276. When `--sync-code` is used, send
the workflow's path relative to the tarball root in the run request
body. The cloud launcher uses it to set `WORKFLOW_FILE` directly to
the in-tree copy at `/project/<path>` and skip uploading the file to
`$HOME` — which means sibling-relative imports like
`../shared/models.ts` resolve against the repo layout instead of `$HOME`.

Relativization rules (validated by the new `relativizeWorkflowPath`):

  * Accepts `workflows/foo.ts`, `./workflows/foo.ts`, and absolute paths
    that live inside `process.cwd()` (the tarball root).
  * Rejects absolute paths outside cwd and any path that would escape
    via `..`. When rejected, the field is simply omitted — the server
    falls back to the pre-existing `$HOME` upload path (unchanged
    behaviour).
  * Normalizes to forward slashes so the Linux-side validator gets a
    consistent shape regardless of the CLI's OS.

Tests: 6/6 pass. The cloud companion PR's validator blocks any path
that doesn't satisfy the same rules, so even if this function emits
something odd the server will drop it and fall back to the legacy path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34426dd1b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +274 to +276
const workflowPath = relativizeWorkflowPath(workflowArg);
if (workflowPath) {
requestBody.workflowPath = workflowPath;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip workflowPath when workflow arg is inline content

resolveWorkflowInput intentionally supports non-file inputs by treating workflowArg as raw workflow text when it doesn’t resolve to a file, but runWorkflow now unconditionally computes and sends workflowPath from that same string. For inline YAML/TS arguments this produces a synthetic path that does not exist in the synced tarball, so with syncCode enabled the launcher can select a non-existent workflow file path instead of the legacy source-upload flow, causing cloud runs to fail for inline invocations. Gate requestBody.workflowPath on inputs that were actually loaded from a filesystem path.

Useful? React with 👍 / 👎.

miyaontherelay and others added 2 commits April 22, 2026 16:30
CI failed because the new test file was written with `node:test` /
`node:assert`, but the repo's test runner is vitest — vitest tried to
import the file and emitted "No test suite found" because none of the
describes registered through its own globals.

  - Imports from 'vitest' (matches packages/cloud/src/auth.test.ts).
  - Switched assert.equal(...) to expect(...).toBe(...) /
    .toBeNull() so the assertions run through vitest's matcher chain.
  - Dropped the `node:assert/strict` import.
  - The "absolute path outside cwd" case now realpath()s os.tmpdir()
    before comparing — symlink-stable on macOS like the tmpRoot.

Verified locally: `npx vitest run packages/cloud/src/workflows.test.ts`
→ 6/6 pass.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +274 to +277
const workflowPath = relativizeWorkflowPath(workflowArg);
if (workflowPath) {
requestBody.workflowPath = workflowPath;
}
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.

🟡 relativizeWorkflowPath sends inline YAML content as a file path to the server

When workflowArg is inline workflow content (not a file path), relativizeWorkflowPath(workflowArg) is still called and can return a non-null garbage value. The CLI supports inline YAML: resolveWorkflowInput at packages/cloud/src/workflows.ts:186-189 falls through to treat workflowArg as raw content when it can't be read as a file and doesn't look like a file path. Since shouldSyncCodeByDefault always returns true (packages/cloud/src/workflows.ts:152-154), the syncCode block executes, and relativizeWorkflowPath is called on the YAML string. For inline content like "version: 1.0\nswarm: ...", path.resolve(cwd, content) produces ${cwd}/${content}, path.relative returns the content itself, which passes all the guards (non-empty, no ../ prefix, not absolute) and gets sent as requestBody.workflowPath. The server would then receive YAML content as a supposed file path in the tarball, potentially causing the server-side launcher to fail to locate the workflow file instead of falling back to the legacy $HOME upload path (which only triggers when workflowPath is absent, not when it's present but invalid).

Prompt for agents
The bug is that relativizeWorkflowPath(workflowArg) is called unconditionally at line 274, but workflowArg might be inline workflow content rather than a file path. This happens when resolveWorkflowInput (line 156-190) falls through to the inline-content return at line 186-189.

The fix should only compute and send workflowPath when the workflow was actually resolved from a file. Options:

1. Add a boolean field (e.g. resolvedFromFile: boolean) to the ResolvedWorkflowInput type and set it to true when readFile succeeds (line 171) and false in the inline fallback (line 186-189). Then guard the relativizeWorkflowPath call with if (input.resolvedFromFile).

2. Alternatively, compare input.workflow !== workflowArg to detect the file case (when a file is read, input.workflow is the file content, not the workflowArg path). This is less explicit but avoids changing the type.

Option 1 is preferred as it's more explicit and robust.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@khaliqgant khaliqgant merged commit aa80270 into main Apr 22, 2026
39 checks passed
@khaliqgant khaliqgant deleted the fix/send-workflow-path branch April 22, 2026 15:06
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.

2 participants