Skip to content

Commit 7577b6f

Browse files
jeffdijdicorpocursoragent
authored
fix(pull): resolve remote results dir by platform UUID, not project name (2.4.6) (#22)
`cf pull` previously looked up `outgoing/results/<project_name>` using whatever name was in the local `.cf/project.json` (or `--project-name`). This broke whenever the project was renamed on the platform or the case differed between the SFTP directory and the local file (e.g. customer had `kyttar` locally but the SFTP directory was `Kyttar`), producing "No results found for project ..." even though the data was right there. Resolution priority (when --project-name is not explicitly passed): 1. Fetch canonical project name from the platform API via the stored platform_project_id. 2. Try outgoing/results/<canonical_name>. 3. If that path is missing, scan outgoing/results/*/config/project.json and match on platform_project_id (authoritative UUID match). 4. Otherwise surface a clear error. The post-download merge step already overwrites the local .cf/project.json with the canonical config (preserving platform_project_id), so the local file self-heals on the next successful pull. --project-name <NAME> still works as a literal-name override / escape hatch. Also aligns chipfoundry_cli/__init__.py (was 2.4.1) with pyproject.toml. Co-authored-by: jdicorpo <jdicorpo@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 77244bd commit 7577b6f

4 files changed

Lines changed: 106 additions & 14 deletions

File tree

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,10 @@ cf pull [--project-name NAME]
655655
**Prerequisites:** `cf login`, `cf link` (or `cf init`), `cf config`
656656

657657
- Downloads project results from SFTP server
658+
- **Resolves the remote results directory by `platform_project_id` (UUID), not by project name** — survives case changes (e.g. `kyttar``Kyttar`) and renames on the platform without manual intervention
659+
- First asks the platform API for the canonical project name and tries `outgoing/results/<canonical_name>`
660+
- If that path is missing, falls back to scanning `outgoing/results/*/config/project.json` for a matching `platform_project_id`
661+
- Pass `--project-name NAME` to bypass UUID resolution and force a literal directory lookup (debugging / unlinked legacy use)
658662
- Saves to `sftp-output/<project_name>/`
659663
- **Automatically updates** your local `.cf/project.json` with the pulled version (preserving the platform link)
660664
- **Syncs with the platform** and displays admin review notes if your project has been reviewed
@@ -859,20 +863,26 @@ The CLI tracks your project submission state through the `submission_state` fiel
859863
- Connects to SFTP server securely
860864
- Shows clean connection status
861865
862-
2. **Download:**
866+
2. **Resolve remote directory by UUID:**
867+
- Looks up the canonical project name from the platform via `platform_project_id`
868+
- Tries `outgoing/results/<canonical_name>` first
869+
- If that path is missing, scans `outgoing/results/*/config/project.json` for a directory whose embedded `platform_project_id` matches yours
870+
- Warns if your local project name differs from the canonical platform name (the local copy is corrected automatically in step 4)
871+
872+
3. **Download:**
863873
- Downloads all project results recursively
864874
- Shows professional download progress
865875
- Saves to `sftp-output/<project_name>/`
866876
867-
3. **Config Update:**
877+
4. **Config Update:**
868878
- **Automatically merges** the pulled `project.json` with your local version (preserving the platform link)
869879
870-
4. **Platform Sync:**
880+
5. **Platform Sync:**
871881
- Sends the updated `project.json` to the platform
872882
- Records the pull timestamp on the platform
873883
- Fetches and displays any admin review notes
874884
875-
5. **Success:**
885+
6. **Success:**
876886
- Shows confirmation of downloaded files, sync status, and review notes
877887
878888
---

chipfoundry_cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""ChipFoundry CLI package: Automate project submission to SFTP."""
2-
__version__ = "2.4.1"
2+
__version__ = "2.4.6"

chipfoundry_cli/main.py

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,6 +2078,9 @@ def push(project_root, sftp_host, sftp_username, sftp_key, project_id, project_n
20782078
@click.option('--sftp-key', type=click.Path(exists=True, dir_okay=False), help='Path to SFTP private key file (defaults to config).', default=None, show_default=False)
20792079
def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
20802080
"""Download results/artifacts from SFTP output dir to local sftp-output/<project_name>."""
2081+
# Track whether the user explicitly passed --project-name (overrides
2082+
# canonical-name resolution via the platform API below).
2083+
explicit_project_name = project_name
20812084
# If .cf/project.json exists in cwd, use its project name as default
20822085
_, cwd_project_name = get_project_json_from_cwd()
20832086
if not project_name and cwd_project_name:
@@ -2142,16 +2145,67 @@ def pull(project_name, output_dir, sftp_host, sftp_username, sftp_key):
21422145
raise click.Abort()
21432146

21442147
try:
2148+
# Resolve the remote results directory.
2149+
#
2150+
# Priority:
2151+
# 1. If the user passed --project-name explicitly, honor that name
2152+
# verbatim (escape hatch / debugging).
2153+
# 2. Otherwise, ask the platform API for the canonical project name
2154+
# via the platform_project_id (UUID) and try that name first.
2155+
# 3. If that directory does not exist on SFTP (e.g. the platform was
2156+
# renamed but the old export directory still has the previous
2157+
# name), scan `outgoing/results/*/config/project.json` and match
2158+
# on `platform_project_id`. This is the authoritative UUID match
2159+
# and survives case changes and renames.
2160+
if explicit_project_name:
2161+
resolved_name = explicit_project_name
2162+
try:
2163+
sftp.stat(f"outgoing/results/{resolved_name}")
2164+
except Exception:
2165+
console.print(f"[yellow]No results found for project '{resolved_name}' on SFTP server.[/yellow]")
2166+
return
2167+
else:
2168+
try:
2169+
platform_proj = _api_get(f"/projects/{platform_id}")
2170+
except SystemExit:
2171+
console.print(f"[red]Could not resolve canonical project name for platform_project_id={platform_id} from the platform API.[/red]")
2172+
raise click.Abort()
2173+
canonical_name = platform_proj.get("name") if isinstance(platform_proj, dict) else None
2174+
if not canonical_name:
2175+
console.print(f"[red]Platform did not return a name for project {platform_id}; cannot resolve SFTP directory.[/red]")
2176+
raise click.Abort()
2177+
2178+
try:
2179+
sftp.stat(f"outgoing/results/{canonical_name}")
2180+
resolved_name = canonical_name
2181+
if cwd_project_name and cwd_project_name != canonical_name:
2182+
console.print(
2183+
f"[yellow]Local project name '{cwd_project_name}' does not match the platform "
2184+
f"name '{canonical_name}'. Using the platform name; your local .cf/project.json "
2185+
f"will be updated after the pull completes.[/yellow]"
2186+
)
2187+
except Exception:
2188+
console.print(
2189+
f"[yellow]'outgoing/results/{canonical_name}' not found on SFTP. "
2190+
f"Searching by project UUID ({platform_id})...[/yellow]"
2191+
)
2192+
matched_dir = _find_remote_results_dir_by_uuid(sftp, platform_id)
2193+
if matched_dir is None:
2194+
console.print(
2195+
f"[yellow]No results found for project '{canonical_name}' (UUID {platform_id}) on SFTP server.[/yellow]"
2196+
)
2197+
return
2198+
resolved_name = matched_dir
2199+
console.print(
2200+
f"[yellow]Found a results directory matching this project's UUID at "
2201+
f"'outgoing/results/{matched_dir}'. The directory name on SFTP differs from the "
2202+
f"platform name '{canonical_name}' — using the SFTP directory.[/yellow]"
2203+
)
2204+
2205+
project_name = resolved_name
21452206
remote_dir = f"outgoing/results/{project_name}"
21462207
output_dir = os.path.join(os.getcwd(), "sftp-output", project_name)
2147-
2148-
# Check if remote directory exists
2149-
try:
2150-
sftp.stat(remote_dir)
2151-
except Exception:
2152-
console.print(f"[yellow]No results found for project '{project_name}' on SFTP server.[/yellow]")
2153-
return
2154-
2208+
21552209
# Create output directory
21562210
os.makedirs(output_dir, exist_ok=True)
21572211

@@ -4735,6 +4789,34 @@ def _load_project_platform_id(project_root: str):
47354789
return data.get('project', {}).get('platform_project_id')
47364790

47374791

4792+
def _find_remote_results_dir_by_uuid(sftp, platform_id: str) -> Optional[str]:
4793+
"""Scan outgoing/results/*/config/project.json for a directory whose embedded
4794+
platform_project_id matches `platform_id`. Returns the bare directory name
4795+
(not the full path) of the first match, or None if no match is found.
4796+
4797+
Used by `cf pull` as a UUID-based fallback when the canonical project
4798+
name from the platform does not resolve to an SFTP directory (e.g. the
4799+
project was renamed on the platform but the old SFTP results directory
4800+
still has the previous name on disk).
4801+
"""
4802+
try:
4803+
dirs = sftp.listdir("outgoing/results")
4804+
except Exception:
4805+
return None
4806+
4807+
for d in dirs:
4808+
cfg_path = f"outgoing/results/{d}/config/project.json"
4809+
try:
4810+
with sftp.open(cfg_path, "r") as f:
4811+
data = json.loads(f.read().decode("utf-8"))
4812+
except Exception:
4813+
continue
4814+
proj = data.get("project", {}) if isinstance(data, dict) else {}
4815+
if isinstance(proj, dict) and proj.get("platform_project_id") == platform_id:
4816+
return d
4817+
return None
4818+
4819+
47384820
def _save_platform_id(project_root: str, platform_id: str, project_name: str = None):
47394821
"""Write platform_project_id (and optionally project name) into .cf/project.json."""
47404822
pj = Path(project_root) / '.cf' / 'project.json'

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "chipfoundry-cli"
3-
version = "2.4.5"
3+
version = "2.4.6"
44
description = "CLI tool to automate ChipFoundry project submission to SFTP server"
55
authors = ["ChipFoundry <marwan.abbas@chipfoundry.io>"]
66
readme = "README.md"

0 commit comments

Comments
 (0)