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
- Use a full RFC 3339 timestamp with timezone, for example 2024-01-01T00:00:00Z.
- Generate the value with date -u +%Y-%m-%dT%H:%M:%SZ so both flags share one format.
- 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
- Format both --travel-to and --done-if-after from the same RFC 3339 generator.
- When the exact cutoff is not important, omit --done-if-after and let the command derive it.
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
- Invalid time for travel_to: '{}'
- 'REMOTE_STORAGE_CONFIG' environment variable must be set to
- unsupported linekind: {s}
- unexpected in-memory layer
- need 1..N positional arguments describing the key, try hex o
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/b8ef22ddf988b0d1.
Report an issue: GitHub.