@@ -2020,7 +2020,17 @@ pub async fn sandbox_create(
20202020 "\u{2022} " . dimmed( ) ,
20212021 ) ;
20222022 let local = Path :: new ( local_path) ;
2023- if * git_ignore && let Ok ( ( base_dir, files) ) = git_sync_files ( local) {
2023+ if !local_upload_path_exists ( local) {
2024+ return Err ( miette:: miette!(
2025+ "local path does not exist: {}" ,
2026+ local. display( )
2027+ ) ) ;
2028+ }
2029+
2030+ if * git_ignore
2031+ && !local_upload_path_is_symlink ( local)
2032+ && let Ok ( ( base_dir, files) ) = git_sync_files ( local)
2033+ {
20242034 sandbox_sync_up_files (
20252035 & effective_server,
20262036 & sandbox_name,
@@ -2031,7 +2041,7 @@ pub async fn sandbox_create(
20312041 & effective_tls,
20322042 )
20332043 . await ?;
2034- } else if local . exists ( ) {
2044+ } else {
20352045 sandbox_sync_up (
20362046 & effective_server,
20372047 & sandbox_name,
@@ -2376,7 +2386,7 @@ pub async fn sandbox_sync_command(
23762386 match ( up, down) {
23772387 ( Some ( local_path) , None ) => {
23782388 let local = Path :: new ( local_path) ;
2379- if !local . exists ( ) {
2389+ if !local_upload_path_exists ( local ) {
23802390 return Err ( miette:: miette!(
23812391 "local path does not exist: {}" ,
23822392 local. display( )
@@ -5412,6 +5422,14 @@ pub fn git_sync_files(local_path: &Path) -> Result<(PathBuf, Vec<String>)> {
54125422 Ok ( ( base_dir, files) )
54135423}
54145424
5425+ pub fn local_upload_path_exists ( path : & Path ) -> bool {
5426+ std:: fs:: symlink_metadata ( path) . is_ok ( )
5427+ }
5428+
5429+ pub fn local_upload_path_is_symlink ( path : & Path ) -> bool {
5430+ std:: fs:: symlink_metadata ( path) . is_ok_and ( |metadata| metadata. file_type ( ) . is_symlink ( ) )
5431+ }
5432+
54155433// ---------------------------------------------------------------------------
54165434// Sandbox policy commands
54175435// ---------------------------------------------------------------------------
@@ -6967,12 +6985,13 @@ mod tests {
69676985 format_gateway_select_items, format_provider_attachment_table, gateway_add,
69686986 gateway_auth_label, gateway_env_override_warning, gateway_select_with, gateway_type_label,
69696987 git_sync_files, http_health_check, image_requests_gpu, import_local_package_mtls_bundle,
6970- inferred_provider_type, package_managed_tls_dirs, parse_cli_setting_value,
6971- parse_credential_expiry_cli_value, parse_credential_expiry_pairs, parse_credential_pairs,
6972- plaintext_gateway_is_remote, progress_step_from_metadata,
6973- provider_profile_allows_refresh_bootstrap, provisioning_timeout_message,
6974- ready_false_condition_message, refresh_status_header, refresh_status_row, resolve_from,
6975- sandbox_should_persist, service_expose_status_error, service_url_for_gateway,
6988+ inferred_provider_type, local_upload_path_exists, local_upload_path_is_symlink,
6989+ package_managed_tls_dirs, parse_cli_setting_value, parse_credential_expiry_cli_value,
6990+ parse_credential_expiry_pairs, parse_credential_pairs, plaintext_gateway_is_remote,
6991+ progress_step_from_metadata, provider_profile_allows_refresh_bootstrap,
6992+ provisioning_timeout_message, ready_false_condition_message, refresh_status_header,
6993+ refresh_status_row, resolve_from, sandbox_should_persist, service_expose_status_error,
6994+ service_url_for_gateway,
69766995 } ;
69776996 use crate :: TEST_ENV_LOCK ;
69786997 use hyper:: StatusCode ;
@@ -7758,6 +7777,31 @@ mod tests {
77587777 assert_eq ! ( files, vec![ "file.txt" , "inner/child.txt" ] ) ;
77597778 }
77607779
7780+ #[ cfg( unix) ]
7781+ #[ test]
7782+ fn local_upload_path_helpers_accept_symlinks ( ) {
7783+ let tmpdir = tempfile:: tempdir ( ) . expect ( "create tmpdir" ) ;
7784+ let target = tmpdir. path ( ) . join ( "target.txt" ) ;
7785+ let link = tmpdir. path ( ) . join ( "link.txt" ) ;
7786+ fs:: write ( & target, "target" ) . expect ( "write target" ) ;
7787+ std:: os:: unix:: fs:: symlink ( "target.txt" , & link) . expect ( "create symlink" ) ;
7788+
7789+ assert ! ( local_upload_path_exists( & link) ) ;
7790+ assert ! ( local_upload_path_is_symlink( & link) ) ;
7791+ }
7792+
7793+ #[ cfg( unix) ]
7794+ #[ test]
7795+ fn local_upload_path_helpers_accept_dangling_symlinks ( ) {
7796+ let tmpdir = tempfile:: tempdir ( ) . expect ( "create tmpdir" ) ;
7797+ let link = tmpdir. path ( ) . join ( "dangling-link.txt" ) ;
7798+ std:: os:: unix:: fs:: symlink ( "missing.txt" , & link) . expect ( "create symlink" ) ;
7799+
7800+ assert ! ( local_upload_path_exists( & link) ) ;
7801+ assert ! ( local_upload_path_is_symlink( & link) ) ;
7802+ assert ! ( !link. exists( ) , "std::path::Path::exists follows symlinks" ) ;
7803+ }
7804+
77617805 #[ test]
77627806 fn gateway_select_uses_explicit_name_without_prompting ( ) {
77637807 let tmpdir = tempfile:: tempdir ( ) . expect ( "create tmpdir" ) ;
0 commit comments