Skip to content

feat: surface phase C multi-repo push results in cloud CLI#775

Open
khaliqgant wants to merge 3 commits intomainfrom
feat/multi-repo-cloud-phase-c-relay
Open

feat: surface phase C multi-repo push results in cloud CLI#775
khaliqgant wants to merge 3 commits intomainfrom
feat/multi-repo-cloud-phase-c-relay

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Apr 23, 2026

Implements the relay-side Phase C changes from specs/multi-repo-cloud-workflows.md, including run-result surfacing for pushed PRs. Generated by ../cloud/workflows/multi-repo-cloud-phase-c-pushback.ts.


Open in Devin Review

github-advanced-security[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

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: 824ab67ace

ℹ️ 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 +757 to 759
(typeof (payload as { hasChanges?: unknown }).hasChanges !== 'boolean' &&
(!('patches' in payload) || typeof (payload as { patches?: unknown }).patches !== 'object'))
) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require sync fields before accepting patch responses

The new validation now accepts payloads that only have patches, but syncWorkflowPatch still returns SyncPatchResponse and downstream cloud sync logic expects hasChanges and patch. When the API returns a multi-repo shape like { patches: { ... } }, result.hasChanges is undefined, so the CLI incorrectly reports "No changes" and never applies any patch content. This turns a clear incompatibility into silent data loss for multi-path runs.

Useful? React with 👍 / 👎.

Comment thread packages/cloud/src/workflows.ts Outdated
}

requestBody.paths = pathSubmissions;
const workflowPath = relativizeWorkflowPathFromRoot(workflowArg, resolvedPathRoots[0]);
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 Compute workflowPath from the matching declared path root

In multi-path mode, workflowPath is always relativized against resolvedPathRoots[0] (the first declared path). If the workflow file is in any other declared path, this produces a .. path and drops workflowPath, forcing the server to fall back to the legacy upload location. That fallback is documented to break sibling-relative imports, so valid multi-repo workflows can fail depending on path ordering.

Useful? React with 👍 / 👎.

- `cloud sync` no longer silently reports "No changes" when the
  server returns a multi-path patch response. It now prints each
  patch under --dry-run and fails loud (with the path names) in
  apply mode so multi-repo runs aren't discarded.
- Relativize `workflowPath` against the declared path that actually
  contains the workflow file, not `resolvedPathRoots[0]`. Previously
  a workflow living in the second declared path produced a `../`
  path and the server fell back to the legacy upload shape.
- Replace the `/^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/` regex in
  `assignPathField` with explicit `indexOf(':')` parsing — fixes
  the polynomial-regex ReDoS flagged by CodeQL.
- `SyncPatchResponse.patch` / `.hasChanges` are now optional to
  match the multi-path response shape.
- Tests for all three behaviors.

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 found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +88 to +98
function stripYamlScalar(raw: string): string {
let value = raw.trim();
const commentIndex = value.search(/\s#/);
if (commentIndex !== -1) {
value = value.slice(0, commentIndex).trim();
}
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
return value.slice(1, -1);
}
return value;
}
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.

🔴 stripYamlScalar strips comments before handling quotes, corrupting quoted values containing #

stripYamlScalar applies comment stripping (\s# detection) before checking for and removing surrounding quotes. For a YAML value like "Fix issue #123", the function first truncates at # (producing "Fix issue), then the quote-matching check fails because the value no longer ends with ". The result is the corrupted string "Fix issue — truncated AND prefixed with a literal quote character.

This directly impacts the pushPrBody field, which is the most likely field to contain # references (e.g., GitHub issue numbers like Fixes #123). Users quoting their values to protect # (the correct YAML approach) still get corrupted output.

Trace of the bug for input: pushPrBody: "Fix issue #123"
  1. raw = "Fix issue #123"
  2. value.search(/\s#/) finds match at index 11 (space before #)
  3. value.slice(0, 11).trim() = "Fix issue
  4. Quote check: starts with " but ends with e → no quote stripping
  5. Returns "Fix issue (truncated + stray leading quote)
Suggested change
function stripYamlScalar(raw: string): string {
let value = raw.trim();
const commentIndex = value.search(/\s#/);
if (commentIndex !== -1) {
value = value.slice(0, commentIndex).trim();
}
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
return value.slice(1, -1);
}
return value;
}
function stripYamlScalar(raw: string): string {
let value = raw.trim();
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
return value.slice(1, -1);
}
const commentIndex = value.search(/\s#/);
if (commentIndex !== -1) {
value = value.slice(0, commentIndex).trim();
}
return value;
}
Open in Devin Review

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

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