---
name: full-path-symlink-sync
slug: full-path-symlink-sync
created: 2026-05-16
completed: 2026-05-16
status: complete
---

# Summary

Closed deploy-pipeline gap discovered during Phase 06.6 operator UAT
(2026-05-17): GitHub Actions `deploy-staging.yml` full-path job image-swapped
via `flyctl deploy` but never updated `/data/client-assets/current/`. The
server's `apps/server/src/static-assets.ts` resolver prefers
`STATIC_ASSETS_DIR=/data/client-assets/current` over the bundled
`/app/public/` whenever the env path exists, so each full-path deploy with
client changes kept serving the previous fast-path release until manual
remediation. Reproduced as `index-DjmLGbtm.js` (on-disk) vs `index-CbpOzv5W.js`
(actually served).

## Change

`.github/workflows/deploy-staging.yml` — full-path job only. Inserted 4 steps
between `flyctl deploy → rebno-staging` and `Dispatch async soak`:

1. `Tarball static output (full-path symlink sync)` — `tar -czf … -C apps/server/public .`
2. `Upload tarball to staging machine (full-path symlink sync)` — `flyctl ssh sftp put`
3. `Upload release script to staging machine (full-path symlink sync)` — `flyctl ssh sftp put`
4. `Sync /data/client-assets/current (atomic swap)` — `flyctl ssh console -C "bash /tmp/client-release-<sha>.sh <sha>"`

Reuses `scripts/client-release.sh` unchanged. Tarball source is
`apps/server/public/` from the earlier `Build client (staging mode)` step —
identical bytes to `/app/public/` baked into the freshly-deployed image. The
release script's existing `mv -T` atomic swap, sanity-check, and GC retention
apply unchanged.

## Files touched

- `.github/workflows/deploy-staging.yml` — 4 new steps in full-path job
- `.planning/STATE.md` — added "Quick Tasks Completed" section + first row
- `.planning/quick/20260516-full-path-symlink-sync/PLAN.md` — this task
- `.planning/quick/20260516-full-path-symlink-sync/SUMMARY.md` — this file

## Out of scope (intentional)

- No changes to `scripts/client-release.sh` — works as-is.
- No changes to `apps/server/src/static-assets.ts` — env-preference behaviour
  is correct (a fast-path release MUST win over an image-baked fallback when
  the operator has run an asset-only deploy).
- No new tests — the new steps are CI-only workflow yaml; verification is
  observational at next full-path deploy.

## Verification path

Next push to `main` with any server-touching diff exercises the new steps.
After the workflow run, paste in the staging tab DevTools console:

```js
({ bundle: document.querySelector('script[type="module"][crossorigin]')?.src })
```

The bundle hash MUST match `apps/server/public/.vite/manifest.json`'s
`index.html.file` from the same build.

## Related

- Memory `fly-fullpath-deploy-data-symlink-gap.md` — root cause + manual
  remediation steps (updated to reference this fix).
- `scripts/client-release.sh` — atomic-swap implementation reused.
- Phase 06.6 STATE note on the discovery (line 459-ish, "Deploy-pipeline gap").
