diff --git a/crates/engine/tests/it/main.rs b/crates/engine/tests/it/main.rs
--- a/crates/engine/tests/it/main.rs
+++ b/crates/engine/tests/it/main.rs
@@ -11,6 +11,7 @@ mod hostile_params;
 mod hostile_sessions;
 mod long_renders;
 mod midi_offset;
+mod min_secs_with_timecode_counter;
 mod mix_required_params;
 mod random_edits;
 mod recording;
diff --git a/crates/engine/tests/it/min_secs_with_timecode_counter.rs b/crates/engine/tests/it/min_secs_with_timecode_counter.rs
new file mode 100644
--- /dev/null
+++ b/crates/engine/tests/it/min_secs_with_timecode_counter.rs
@@ -0,0 +1,11 @@
+use serde_json::json;
+use soundcraft_engine::Engine;
+
+#[test]
+fn min_secs_string_is_not_misread_as_timecode_when_the_counter_is_timecode() {
+    let mut e = Engine::default();
+    e.execute("view.main_counter", &json!({"format": "timecode"})).unwrap();
+    e.execute("edit.select", &json!({"start": "0:12.500", "end": "0:20.000"})).unwrap();
+    let sel = e.session().edit.selection;
+    assert_eq!((sel.start, sel.end), (600_000, 960_000));
+}
diff --git a/crates/time/src/format.rs b/crates/time/src/format.rs
--- a/crates/time/src/format.rs
+++ b/crates/time/src/format.rs
@@ -115,7 +115,13 @@ pub fn parse_position(
             let v = sr.samples(m * 60.0 + s);
             Ok(if neg { -v } else { v })
         }
-        TimeFormat::Timecode => Ok(Timecode::parse(t)?.to_samples(rate, sr).saturating_sub(tc_start)),
+        TimeFormat::Timecode => {
+            let tc = Timecode::parse(t)?;
+            if tc.minutes >= 60 || tc.seconds >= 60 || tc.frames >= rate.nominal().max(1) {
+                return Err(err());
+            }
+            Ok(tc.to_samples(rate, sr).saturating_sub(tc_start))
+        }
         TimeFormat::FeetFrames => {
             let (ft, fr) = t.split_once('+').ok_or_else(err)?;
             let ft: i64 = ft.trim().parse().map_err(|_| err())?;
