spacedriveapp/spacedrive · error · anyhow::Error

Failed to read service account file: {}

Error message

Failed to read service account file: {}

What it means

Returned by SyncResolver::calculate_operations when SyncMode::from_str(&conduit.sync_mode) fails on the conduit's stored mode string. This is the same parse that sync_now performs later (mod.rs:93); the resolver hits it first while choosing a strategy (Mirror/Bidirectional/Selective in the match below). An unrecognized string aborts operation calculation.

Source

Thrown at apps/cli/src/domains/cloud/setup.rs:315

		},
	};

	execute_add_cloud(ctx, input).await
}

async fn add_gcs_interactive(ctx: &Context) -> Result<()> {
	let name = text("Volume name (e.g., 'My GCS Bucket')", false)?.unwrap();
	let bucket = text("Bucket name", false)?.unwrap();
	let root = text("Root path (leave empty for entire bucket)", true)?;
	let endpoint = text("Custom endpoint (leave empty for default)", true)?;

	println!("\nService Account Setup:");
	println!("  You'll need a service account JSON key from Google Cloud Console");
	println!("  Visit: https://console.cloud.google.com/iam-admin/serviceaccounts\n");

	let service_account_path = text("Path to service account JSON file", false)?.unwrap();
	let credential = std::fs::read_to_string(&service_account_path)
		.map_err(|e| anyhow::anyhow!("Failed to read service account file: {}", e))?;

	println!("\nSummary:");
	println!("  Provider: Google Cloud Storage");
	println!("  Name:     {}", name);
	println!("  Bucket:   {}", bucket);
	if let Some(ref r) = root {
		println!("  Root:     {}", r);
	}
	if let Some(ref e) = endpoint {
		println!("  Endpoint: {}", e);
	}
	println!();

	confirm_or_abort("Add this cloud volume?", false)?;

	let input = VolumeAddCloudInput {
		service: CloudServiceType::GoogleCloudStorage,
		display_name: name.clone(),

View on GitHub (pinned to 6dfeccf211)

Solutions

  1. Read the stored value: SELECT sync_mode FROM sync_conduit WHERE id = <id> and compare with the strings SyncMode::from_str accepts (see the sync_conduit entity).
  2. Update the row to a valid mode string ('mirror' | 'bidirectional' | 'selective').
  3. Make create_conduit's mode input a typed enum end-to-end so only valid strings can be persisted.

Example fix

-- before
SELECT sync_mode FROM sync_conduit; -- 'two-way'

-- after
UPDATE sync_conduit SET sync_mode = 'bidirectional' WHERE sync_mode = 'two-way';
Defensive patterns

Strategy: validation

Validate before calling

fn sync_mode_is_valid(s: &str) -> bool {
    sync_conduit::SyncMode::from_str(s).is_some()
}

// before calculate_operations:
if !sync_mode_is_valid(&conduit.sync_mode) {
    return Err(anyhow::anyhow!("conduit {} has corrupt sync_mode '{}'", conduit.id, conduit.sync_mode));
}

Try / catch

match resolver.calculate_operations(&conduit).await {
    Ok(ops) => Ok(ops),
    Err(e) if e.to_string() == "Invalid sync mode" => {
        // fail fast with the offending value for a targeted DB fix
        Err(anyhow::anyhow!("fix sync_mode='{}' on conduit {}", conduit.sync_mode, conduit.id))
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: calculate_operations runs on a conduit whose sync_mode column holds a string outside the accepted variants — version drift in enum variant names, manual DB edits, or a client that submitted free text that was stored verbatim via create_conduit's sync_mode handling.

Common situations: Upgrading between versions where SyncMode variants were renamed; seed/import scripts writing arbitrary strings; copy-paste typos in manual updates.

Related errors


AI-assisted analysis of spacedriveapp/spacedrive@6dfeccf211 (2026-08-16). Data as JSON: /api/errors/211f2cbfdf21ecf5. Report an issue: GitHub.