diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 1da2a700..a6367e76 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -10,7 +10,8 @@ use helix_stdx::path::get_relative_path;
 use helix_view::{
     align_view,
     document::{DocumentOpenError, DocumentSavedEventResult},
-    editor::{ConfigEvent, EditorEvent},
+    editor::{ConfigEvent, EditorEvent, TICK_DURATION},
+    events::DidRequestInlineBlame,
     graphics::Rect,
     theme,
     tree::Layout,
@@ -32,7 +33,7 @@ use crate::{
 use log::{debug, error, info, warn};
 #[cfg(not(feature = "integration"))]
 use std::io::stdout;
-use std::{io::stdin, path::Path, sync::Arc};
+use std::{io::stdin, path::Path, sync::Arc, time::Duration};
 
 #[cfg(not(windows))]
 use anyhow::Context;
@@ -590,6 +591,18 @@ impl Application {
         ));
     }
 
+    async fn handle_tick(&mut self) {
+        self.editor.ticks_elapsed += 1;
+        // request an inline blame refresh every this amount of ticks
+        const INLINE_BLAME_REQUEST_TIME: u128 =
+            Duration::from_millis(150).as_nanos() / TICK_DURATION.as_nanos();
+        if self.editor.ticks_elapsed % INLINE_BLAME_REQUEST_TIME == 0 {
+            helix_event::dispatch(DidRequestInlineBlame {
+                editor: &mut self.editor,
+            })
+        };
+    }
+
     #[inline(always)]
     pub async fn handle_editor_event(&mut self, event: EditorEvent) -> bool {
         log::debug!("received editor event: {:?}", event);
@@ -626,6 +639,9 @@ impl Application {
                     return true;
                 }
             }
+            EditorEvent::Tick => {
+                self.handle_tick().await;
+            }
         }
 
         false
diff --git a/helix-term/src/events.rs b/helix-term/src/events.rs
index 15d81152..14e9274b 100644
--- a/helix-term/src/events.rs
+++ b/helix-term/src/events.rs
@@ -1,7 +1,8 @@
 use helix_event::{events, register_event};
 use helix_view::document::Mode;
 use helix_view::events::{
-    DiagnosticsDidChange, DocumentDidChange, DocumentFocusLost, SelectionDidChange,
+    DiagnosticsDidChange, DidRequestInlineBlame, DocumentDidChange, DocumentFocusLost,
+    SelectionDidChange,
 };
 
 use crate::commands;
@@ -19,6 +20,7 @@ pub fn register() {
     register_event::<PostCommand>();
     register_event::<DocumentDidChange>();
     register_event::<DocumentFocusLost>();
+    register_event::<DidRequestInlineBlame>();
     register_event::<SelectionDidChange>();
     register_event::<DiagnosticsDidChange>();
 }
diff --git a/helix-term/src/handlers/blame.rs b/helix-term/src/handlers/blame.rs
index c4e53919..12cb58b2 100644
--- a/helix-term/src/handlers/blame.rs
+++ b/helix-term/src/handlers/blame.rs
@@ -1,10 +1,13 @@
 use std::time::Duration;
 
 use helix_event::{register_hook, send_blocking};
-use helix_view::handlers::{BlameEvent, Handlers};
+use helix_view::{
+    events::DidRequestInlineBlame,
+    handlers::{BlameEvent, Handlers},
+};
 use tokio::{task::JoinHandle, time::Instant};
 
-use crate::{events::PostCommand, job};
+use crate::job;
 
 #[derive(Default)]
 pub struct BlameHandler {
@@ -77,13 +80,14 @@ impl helix_event::AsyncHook for BlameHandler {
 
 pub(super) fn register_hooks(handlers: &Handlers) {
     let tx = handlers.blame.clone();
-    register_hook!(move |event: &mut PostCommand<'_, '_>| {
-        let version_control_config = &event.cx.editor.config().version_control;
+    register_hook!(move |event: &mut DidRequestInlineBlame<'_>| {
+        let version_control_config = &event.editor.config().version_control;
         if !version_control_config.inline_blame {
             return Ok(());
         }
 
-        let (view, doc) = current!(event.cx.editor);
+        let (view, doc) = current!(event.editor);
+
         let Some(file) = doc.path() else {
             return Ok(());
         };
@@ -93,10 +97,21 @@ pub(super) fn register_hooks(handlers: &Handlers) {
             return Ok(());
         };
 
+        if let Some(cached) = &mut event.editor.blame_cache {
+            // don't update the blame if we haven't moved to a different line
+            if (view.id, cursor_line) == *cached {
+                return Ok(());
+            } else {
+                *cached = (view.id, cursor_line)
+            }
+        };
+
         let Some(hunks) = doc.diff_handle() else {
             return Ok(());
         };
 
+        log::error!("updated blame!");
+
         let (inserted_lines_count, deleted_lines_count) = hunks
             .load()
             .inserted_and_deleted_before_line(cursor_line as usize);
@@ -109,7 +124,7 @@ pub(super) fn register_hooks(handlers: &Handlers) {
                 deleted_lines_count,
                 inserted_lines_count,
                 // ok to clone because diff_providers is very small
-                diff_providers: event.cx.editor.diff_providers.clone(),
+                diff_providers: event.editor.diff_providers.clone(),
                 // ok to clone because blame_format is likely to be about 30 characters or less
                 blame_format: version_control_config.inline_blame_format.clone(),
             },
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index e9a347f8..45475326 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -21,7 +21,7 @@ use helix_vcs::DiffProviderRegistry;
 use futures_util::stream::select_all::SelectAll;
 use futures_util::{future, StreamExt};
 use helix_lsp::{Call, LanguageServerId};
-use tokio_stream::wrappers::UnboundedReceiverStream;
+use tokio_stream::wrappers::{IntervalStream, UnboundedReceiverStream};
 
 use std::{
     borrow::Cow,
@@ -37,7 +37,7 @@ use std::{
 
 use tokio::{
     sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
-    time::{sleep, Duration, Instant, Sleep},
+    time::{interval, sleep, Duration, Instant, Sleep},
 };
 
 use anyhow::{anyhow, bail, Error};
@@ -1103,6 +1103,8 @@ pub struct Editor {
     pub auto_pairs: Option<AutoPairs>,
 
     pub idle_timer: Pin<Box<Sleep>>,
+    pub tick: IntervalStream,
+    pub ticks_elapsed: u128,
     redraw_timer: Pin<Box<Sleep>>,
     last_motion: Option<Motion>,
     pub last_completion: Option<CompleteAction>,
@@ -1128,6 +1130,7 @@ pub struct Editor {
 
     pub mouse_down_range: Option<Range>,
     pub cursor_cache: CursorCache,
+    pub blame_cache: Option<(ViewId, u32)>,
 }
 
 pub type Motion = Box<dyn Fn(&mut Editor)>;
@@ -1139,6 +1142,7 @@ pub enum EditorEvent {
     LanguageServerMessage((LanguageServerId, Call)),
     DebuggerEvent(dap::Payload),
     IdleTimer,
+    Tick,
     Redraw,
 }
 
@@ -1193,6 +1197,9 @@ pub enum CloseError {
     SaveError(anyhow::Error),
 }
 
+/// A tick happens every this amount of milliseconds
+pub const TICK_DURATION: Duration = Duration::from_millis(1000 / 60);
+
 impl Editor {
     pub fn new(
         mut area: Rect,
@@ -1238,6 +1245,8 @@ impl Editor {
             status_msg: None,
             autoinfo: None,
             idle_timer: Box::pin(sleep(conf.idle_timeout)),
+            tick: IntervalStream::new(interval(TICK_DURATION)),
+            ticks_elapsed: 0,
             redraw_timer: Box::pin(sleep(Duration::MAX)),
             last_motion: None,
             last_completion: None,
@@ -1250,6 +1259,7 @@ impl Editor {
             handlers,
             mouse_down_range: None,
             cursor_cache: CursorCache::default(),
+            blame_cache: None,
         }
     }
 
@@ -2149,6 +2159,9 @@ impl Editor {
                 _ = &mut self.idle_timer  => {
                     return EditorEvent::IdleTimer
                 }
+                _ = &mut self.tick.next() => {
+                    return EditorEvent::Tick
+                }
             }
         }
     }
diff --git a/helix-view/src/events.rs b/helix-view/src/events.rs
index eb97268c..2c6b5195 100644
--- a/helix-view/src/events.rs
+++ b/helix-view/src/events.rs
@@ -15,4 +15,5 @@ events! {
     DiagnosticsDidChange<'a> { editor: &'a mut Editor, doc: DocumentId }
     // called **after** a document loses focus (but not when its closed)
     DocumentFocusLost<'a> { editor: &'a mut Editor, doc: DocumentId }
+    DidRequestInlineBlame<'a> { editor: &'a mut Editor }
 }