neondatabase/neon · error

Invalid time for done_if_after: '{}'

Error message

Invalid time for done_if_after: '{}'

What it means

The optional --done-if-after argument of time-travel-remote-prefix is parsed with the same RFC 3339 parser (humantime::parse_rfc3339) as --travel_to. A value that is not full RFC 3339 is rejected with this error. When the flag is omitted, the command derives the value from the current time itself.

Source

Thrown at pageserver/ctl/src/main.rs:161

        Commands::AnalyzeLayerMap(cmd) => {
            layer_map_analyzer::main(&cmd).await?;
        }
        Commands::PrintLayerFile(cmd) => {
            if let Err(e) = read_pg_control_file(&cmd.path) {
                println!(
                    "Failed to read input file as a pg control one: {e:#}\n\
                    Attempting to read it as layer file"
                );
                print_layerfile(&cmd.path).await?;
            }
        }
        Commands::TimeTravelRemotePrefix(cmd) => {
            let timestamp = humantime::parse_rfc3339(&cmd.travel_to)
                .map_err(|_e| anyhow::anyhow!("Invalid time for travel_to: '{}'", cmd.travel_to))?;

            let done_if_after = if let Some(done_if_after) = &cmd.done_if_after {
                humantime::parse_rfc3339(done_if_after).map_err(|_e| {
                    anyhow::anyhow!("Invalid time for done_if_after: '{}'", done_if_after)
                })?
            } else {
                const SAFETY_MARGIN: Duration = Duration::from_secs(3);
                tokio::time::sleep(SAFETY_MARGIN).await;
                // Convert to string representation and back to get rid of sub-second values
                let done_if_after = SystemTime::now();
                tokio::time::sleep(SAFETY_MARGIN).await;
                done_if_after
            };

            let timestamp = strip_subsecond(timestamp);
            let done_if_after = strip_subsecond(done_if_after);

            let Some(prefix) = validate_prefix(&cmd.prefix) else {
                println!("specified prefix '{}' failed validation", cmd.prefix);
                return Ok(());
            };
            let config = RemoteStorageConfig::from_toml_str(&cmd.config_toml_str)?;

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Use a full RFC 3339 timestamp with timezone, for example 2024-01-01T00:00:00Z.
  2. Generate the value with date -u +%Y-%m-%dT%H:%M:%SZ so both flags share one format.
  3. If unsure, omit --done-if-after and let the command derive it from the current time.

Example fix

# before
pageserver_ctl time-travel-remote-prefix --travel-to "$TS" --done-if-after "Jan 1 12:00:00 2024" ...

# after
pageserver_ctl time-travel-remote-prefix --travel-to "$TS" --done-if-after "2024-01-01T12:00:00Z" ...
Defensive patterns

Strategy: validation

Validate before calling

if let Some(done_if_after) = &cmd.done_if_after {
    anyhow::ensure!(
        humantime::parse_rfc3339(done_if_after).is_ok(),
        "done_if_after must be RFC 3339, e.g. 2024-01-01T00:00:00Z"
    );
}

Type guard

fn is_rfc3339(s: &str) -> bool {
    humantime::parse_rfc3339(s).is_ok()
}

Prevention

When it happens

Trigger: Passing --done-if-after with a non-RFC 3339 value such as a date-only string or an epoch number.

Common situations: Filling both timestamp flags from the same script where one is formatted differently; locale-specific date output pasted into the command.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/b8ef22ddf988b0d1. Report an issue: GitHub.