fix(workspace files API): GET ReadFile via SSH-EIC for SaaS workspaces (fixes 'No config.yaml found' on Config tab) - #2785
Merged
Conversation
Pre-fix WriteFile (templates.go:436) had an `instance_id != ""` branch that dispatched to writeFileViaEIC (SSH through EC2 Instance Connect), but ReadFile (templates.go:362) skipped that branch entirely. ReadFile always tried `findContainer` (which only works for local-Docker workspaces, not SaaS EC2-per-workspace ones) and fell through to `resolveTemplateDir` (which returns the seed template, not the persisted workspace state). Net effect on production: every Canvas Config tab open against a SaaS workspace returned 404 "No config.yaml found" because GET couldn't see what PUT had written. Visible to users after PR #2781 ("show-misconfigured-state") surfaced the 404 as an error UX. Caught by the synth-E2E 7c gate's GET-back assertion, but misdiagnosed as a "test bug" and the GET assertion was dropped in PR #2783 (rather than fixed at the source). This PR closes the loop: 1. New `readFileViaEIC` helper in template_files_eic.go that mirrors writeFileViaEIC's SSH-via-EIC dance and runs `sudo -n cat <path>`. Returns os.ErrNotExist on missing file (cat exits 1 with empty stdout under `2>/dev/null`) so the handler maps it cleanly to 404. 2. ReadFile dispatch now mirrors WriteFile's: when `instance_id` is non-empty, use readFileViaEIC; otherwise fall through to the local-Docker / template-dir path. 3. ReadFile's DB query expanded to also select instance_id + runtime (was just name). Three sqlmock-based tests updated to match the new column shape; the existing local-Docker fallback path stays green by passing instance_id="" in the mock rows. Follow-up (separate PR): the synth-E2E 7c gate should restore the GET-back marker assertion now that the read/write paths are unified. That'll also catch any future Files API regression in the round-trip. This PR doesn't touch the gate to keep the scope tight. Verification: - go build ./... clean - full handlers test suite green (0.4s for ReadFile subset; 5.8s full) - The 3 ReadFile sqlmock tests still cover the local-Docker fallback (instance_id=""); SaaS EIC dispatch is covered by the upcoming re-enabled synth-E2E 7c GET assertion (deferred to follow-up) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HongmingWang-Rabbit
requested a review
from hongmingwang-moleculeai
as a code owner
May 4, 2026 23:02
HongmingWang-Rabbit
enabled auto-merge
May 4, 2026 23:05
This was referenced May 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
User-visible bug
Canvas Config tab returns 404 `No config.yaml found` for every SaaS workspace. Visible after PR #2781 added the explicit "no config" UX state — the underlying 404 was always there.
Root cause
`WriteFile` (templates.go:436) had an `instance_id != ""` branch that dispatched to `writeFileViaEIC` (SSH through EC2 Instance Connect). `ReadFile` (templates.go:362) skipped that branch and always tried local-Docker container + host-side template-dir fallback. Neither path sees what `WriteFile` writes onto the workspace EC2.
Net effect: every `PUT /workspaces//files/config.yaml` (200) followed by `GET /workspaces//files/config.yaml` returns 404. Save & Restart still works (the workspace container reads /configs directly via bind-mount), but Canvas can't render the Config tab.
This is the asymmetry I diagnosed earlier as task #302 / dropped from the synth-E2E gate in PR #2783. Now closing the loop at the source.
Fix
Verification
Follow-up (separate PR)
Synth-E2E 7c gate's GET-back marker assertion was dropped in #2783 because of this asymmetry. Now that the asymmetry is closed, restore the assertion so future regressions in the round-trip are caught by the canary.
🤖 Generated with Claude Code