Skip to content

fix(server/workflows): resolve project workflows with .yml extension#1711

Open
jasonjack-jay wants to merge 1 commit into
coleam00:devfrom
jasonjack-jay:fix/workflow-detail-yml-extension
Open

fix(server/workflows): resolve project workflows with .yml extension#1711
jasonjack-jay wants to merge 1 commit into
coleam00:devfrom
jasonjack-jay:fix/workflow-detail-yml-extension

Conversation

@jasonjack-jay
Copy link
Copy Markdown

@jasonjack-jay jasonjack-jay commented May 17, 2026

Summary

GET /api/workflows/:name and DELETE /api/workflows/:name only probed ${name}.yaml on disk, but the discovery scanner that backs GET /api/workflows accepts both .yaml and .yml. A project workflow named phase-0-spike.yml therefore appeared in the list endpoint but 404'd on the detail endpoint and could not be deleted via the API.

Repro

  1. In a project's .archon/workflows/, create phase-0-spike.yml (note the extension) with any valid workflow.
  2. GET /api/workflows?cwd=<project> — workflow appears in the list.
  3. GET /api/workflows/phase-0-spike?cwd=<project> — 404.
  4. The Archon UI's run-detail page (/workflows/runs/<id>) fetches this endpoint and renders broken for any run whose workflow file uses .yml.

Fix

  • GET /api/workflows/:name now probes ${name}.yaml then ${name}.yml in each scope (project, home, default). Extracted as a small inline helper to avoid duplicating the same logic three times.
  • DELETE /api/workflows/:name probes both extensions before returning 404.
  • PUT (save) is unchanged — writes continue to use .yaml as the canonical extension for newly-created workflows. Existing .yml files remain readable and deletable.
  • Bundled defaults still key on ${name}.yaml (synthetic in-memory key, not a filesystem path).

Tests

  • Added regression test: phase-0-spike.yml returns 200 with `source: project` and `filename: phase-0-spike.yml`.
  • 36/36 tests in api.workflows.test.ts pass (was 35).
  • `tsc --noEmit` on `packages/server` clean.

Summary by CodeRabbit

  • New Features

    • Workflow retrieval and deletion operations now support both .yaml and .yml file extensions, providing greater flexibility in file naming conventions.
  • Tests

    • Added test coverage verifying proper discovery and serving of workflow files with .yml extensions.

Review Change Stack

`GET /api/workflows/:name` and `DELETE /api/workflows/:name` constructed
the on-disk filename as `${name}.yaml` and only tried that one extension.
The discovery scanner in `workflow-discovery.ts` (the source for
`GET /api/workflows`) accepts both `.yaml` and `.yml`, so a project that
named its workflow `phase-0-spike.yml` would appear in the list endpoint
but 404 on the detail endpoint and could not be deleted via the API.

This is the same scope-mismatch class as the trailing-newline / case-sensitivity
bugs that crop up whenever two endpoints derive paths independently — fix it
once at the read site rather than forcing every author to rename files.

Changes:
- GET /api/workflows/:name now probes `${name}.yaml` then `${name}.yml`
  in each scope (project, home, default). Extracted as a small inline
  helper so the same logic isn't duplicated three times.
- DELETE /api/workflows/:name probes both extensions before returning 404.
- PUT (save) is unchanged: writes continue to use `.yaml` as the
  canonical extension for newly-created workflows. Existing `.yml` files
  remain readable and deletable.
- Bundled defaults are still keyed on `${name}.yaml` (the suffix is a
  synthetic in-memory key there, not a filesystem path).

Tests:
- Added regression: `phase-0-spike.yml` in `<cwd>/.archon/workflows/`
  returns 200 with `source: project` and `filename: phase-0-spike.yml`.
- All 36 tests in `api.workflows.test.ts` pass (was 35).
- `tsc --noEmit` on `packages/server` is clean.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

📝 Walkthrough

Walkthrough

The PR updates workflow file handling to recognize both .yaml and .yml extensions. It adds a shared helper function that tries reading each extension sequentially, integrates this into GET and DELETE endpoints to search project and home-scoped directories, and adds a test confirming .yml file discovery.

Changes

Workflow File Extension Support

Layer / File(s) Summary
Workflow reader helper and GET handler
packages/server/src/routes/api.ts
New tryReadWorkflowAt(dir) helper tries .yaml then .yml from a target directory. The GET /api/workflows/:name handler uses it for project and home-scoped lookups, preserving the bundled-then-default fallback order and parse error handling.
DELETE handler for both extensions
packages/server/src/routes/api.ts
DELETE /api/workflows/:name iterates through .yaml and .yml extensions when unlinking from the target directory; returns success on the first successful deletion or 404 after both fail.
Test for .yml workflow discovery
packages/server/src/routes/api.workflows.test.ts
New test case validates that .yml workflow files are discovered as project-scoped workflows, with correct filename and parsed content returned.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • coleam00/Archon#1524: The main changes update the GET and DELETE /api/workflows/:name handlers to search both .yaml and .yml across project, home-scoped, and bundled/default locations—directly addressing the same get/delete workflow lookup logic and home-scoped omission described in the retrieved issue.

Possibly related PRs

Poem

🐇 Hop, hop, hooray! Extensions grow so fine,
Both .yaml and .yml workflows now align,
The helper hops through folders with a bound,
Finding workflows, never frowned—test-proven, safe and sound!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The description provides a clear problem statement, reproduction steps, detailed fix explanation, and test validation, but lacks structured sections like UX Journey, Architecture Diagram, Risk assessment, and other template sections. Consider adding missing template sections such as Architecture Diagram, Label Snapshot, Security Impact, Compatibility, Human Verification, and Rollback Plan to fully align with the repository's description template.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: resolving project workflows with .yml extension for the GET /api/workflows/:name endpoint.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/server/src/routes/api.workflows.test.ts (1)

280-307: ⚡ Quick win

Add a matching .yml regression for DELETE to lock the full fix surface.

This PR fixes both GET and DELETE extension handling, but only GET .yml is asserted. Add a sibling DELETE .yml test to prevent regressions.

Proposed test addition
 describe('DELETE /api/workflows/:name', () => {
+  test('removes existing .yml workflow file and returns deleted:true', async () => {
+    const testDir = join(tmpdir(), `wf-del-yml-test-${Date.now()}`);
+    const workflowDir = join(testDir, '.archon', 'workflows');
+    await mkdir(workflowDir, { recursive: true });
+    await writeFile(
+      join(workflowDir, 'to-delete-yml.yml'),
+      'name: to-delete-yml\ndescription: y\nnodes:\n  - id: z\n    command: z\n'
+    );
+
+    try {
+      const app = createTestApp();
+      registerApiRoutes(app, {} as WebAdapter, {} as ConversationLockManager);
+      mockListCodebases.mockImplementationOnce(async () => [{ default_cwd: testDir }]);
+
+      const response = await app.request(`/api/workflows/to-delete-yml?cwd=${testDir}`, {
+        method: 'DELETE',
+      });
+      expect(response.status).toBe(200);
+      const body = (await response.json()) as { deleted: boolean; name: string };
+      expect(body.deleted).toBe(true);
+      expect(body.name).toBe('to-delete-yml');
+    } finally {
+      await rm(testDir, { recursive: true, force: true });
+    }
+  });
As per coding guidelines, "**/*.{ts,tsx,test.ts}: ... keep tests deterministic with no flaky timing or network dependence without guardrails."
🤖 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 `@packages/server/src/routes/api.workflows.test.ts` around lines 280 - 307, Add
a sibling test mirroring the GET `.yml` case that exercises DELETE handling:
create a test named like "returns project workflow when file uses .yml extension
(matches discovery) - DELETE", reuse the same setup steps (create testDir,
workflowDir, write phase-0-spike.yml), instantiate the app with createTestApp()
and registerApiRoutes(app, {} as WebAdapter, {} as ConversationLockManager),
mockListCodebases.mockImplementationOnce to return [{ default_cwd: testDir }],
call app.request('/api/workflows/phase-0-spike?cwd=' + testDir, { method:
'DELETE' }), and assert the response status is 200 and the response body
contains source === 'project' and filename === 'phase-0-spike.yml'; finally,
cleanup testDir with rm(...).
🤖 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.

Nitpick comments:
In `@packages/server/src/routes/api.workflows.test.ts`:
- Around line 280-307: Add a sibling test mirroring the GET `.yml` case that
exercises DELETE handling: create a test named like "returns project workflow
when file uses .yml extension (matches discovery) - DELETE", reuse the same
setup steps (create testDir, workflowDir, write phase-0-spike.yml), instantiate
the app with createTestApp() and registerApiRoutes(app, {} as WebAdapter, {} as
ConversationLockManager), mockListCodebases.mockImplementationOnce to return [{
default_cwd: testDir }], call app.request('/api/workflows/phase-0-spike?cwd=' +
testDir, { method: 'DELETE' }), and assert the response status is 200 and the
response body contains source === 'project' and filename ===
'phase-0-spike.yml'; finally, cleanup testDir with rm(...).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a1f98667-c627-4091-870b-3cc8bb220683

📥 Commits

Reviewing files that changed from the base of the PR and between 7bdf931 and e72ff32.

📒 Files selected for processing (2)
  • packages/server/src/routes/api.ts
  • packages/server/src/routes/api.workflows.test.ts

@Wirasm
Copy link
Copy Markdown
Collaborator

Wirasm commented May 19, 2026

Review Summary

Verdict: minor-fixes-needed

Extends the workflow CRUD API to support .yml extension (matching the discovery scanner) and adds source=global for home-scoped workflow management via the Web UI. The implementation is clean and consistent with existing patterns. Three test gaps on the new .yml fallback paths need to be addressed before merging.

Blocking issues

None — no CRITICAL findings.

Suggested fixes

  • packages/server/src/routes/api.ts:2469–2489 (DELETE handler — .yml loop untested): Add a test that creates workflow.yml in a temp dir, calls DELETE, asserts { deleted: true } and the file is removed. Repeat for source=global.

  • packages/server/src/routes/api.ts:2264–2282 (GET handler — home-scope .yml fallback untested): Create ~/.archon/workflows/phase-0-spike.yml (via ARCHON_HOME env), delete any project-level file, request GET phase-0-spike without cwd, assert source: 'global' and filename: 'phase-0-spike.yml'.

  • packages/server/src/routes/api.ts:2343–2358 (GET handler — default-scope .yml fallback untested): With ARCHON_HOME=/nonexistent and no codebase registered, create a temp default workflows dir with workflow.yml, mock getDefaultWorkflowsPath to return it, assert the workflow is returned with source: 'bundled' and correct filename.

  • packages/server/src/routes/api.ts:2267–2269 (permission-denied re-throw untested): Mock readFile to throw EACCES, assert the handler returns 500 with "Failed to read workflow".

Minor / nice-to-have

  • Delete handler ENOENT catch block (api.ts): The continue inside the catch block implicitly skips the error log before jumping to the next iteration. Move the error log inside an else block so the structure matches the intent — this prevents a future maintainer from misreading it as a bug.

  • tryReadWorkflowAt inline doc: Add a brief comment above the helper: // Returns null if neither extension exists (caller should try next source) so the intentional fallback is self-documenting.

  • Comment line number (api.ts): The new comment references "line 119" of workflow-discovery.ts. Optional: drop the line number to avoid a maintenance touchpoint when the file is edited.

Compliments

  • The tryReadWorkflowAt helper mirrors the discovery scanner's filter exactly — a clean way to eliminate the inconsistency between list and fetch endpoints.
  • The source=global addition unlocks the Web UI to manage home-scoped workflows directly, which was previously only possible via CLI. Good scope.
  • The test for project-scope .yml GET lookup is well-structured and isolated.

Reviewed via maintainer-review-pr workflow (Pi/Minimax). Aspects run: code-review, error-handling, test-coverage, comment-quality.

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