<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Amrit Dash</title>
    <description>The latest articles on DEV Community by Amrit Dash (@theaogguy).</description>
    <link>https://dev.to/theaogguy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F528594%2F7374b7b4-ba17-42c4-be32-82d8fd5f8402.jpeg</url>
      <title>DEV Community: Amrit Dash</title>
      <link>https://dev.to/theaogguy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/theaogguy"/>
    <language>en</language>
    <item>
      <title>I made `git push` refuse to work until I touch grass</title>
      <dc:creator>Amrit Dash</dc:creator>
      <pubDate>Sat, 10 Oct 2026 13:13:54 +0000</pubDate>
      <link>https://dev.to/theaogguy/i-made-git-push-refuse-to-work-until-i-touch-grass-138p</link>
      <guid>https://dev.to/theaogguy/i-made-git-push-refuse-to-work-until-i-touch-grass-138p</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge: Week 1, Touch Grass&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;For the past couple of weeks I've been losing track of time. I'd look up, see 1 o'clock, and honestly not know if it was the afternoon or the middle of the night.&lt;/p&gt;

&lt;p&gt;I was so deep in a project that I'd stopped taking breaks. That's what made me build &lt;strong&gt;touch-grass&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's a git pre-push hook. After 3 hours indoors (you can change that), this happens. Here's a real one from testing, with the limit set to 1 minute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git push

🌱 touch-grass: 9m since you last saw the sky. Push blocked.
   Run `touchgrass prove`, scan the code with your phone and take a photo outside.
   (Yes, `git push --no-verify` works. You'll know.)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get your push back, you run &lt;code&gt;touchgrass prove&lt;/code&gt;. It prints a QR code. You scan it, your phone opens the camera, and you go outside and take a photo.&lt;/p&gt;

&lt;p&gt;Then &lt;strong&gt;Gemma 4, running on your own laptop&lt;/strong&gt;, looks at it and decides if it counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;I didn't record a video, so here's a real &lt;code&gt;prove&lt;/code&gt; run from testing instead. I ran it over the tunnel, scanned the code and walked outside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ touchgrass prove --tunnel
📱 On your phone (anywhere), open:
   https://….trycloudflare.com/?t=…
   Model: gemma4:e4b via http://localhost:11434
   Waiting for a photo of the outside world… (Ctrl+C to give up)

✅ Grass confirmed. A hilly landscape with scattered houses under a cloudy sky.
   "It looks like you've found a much better environment to debug than your IDE."
   Push unlocked.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdst7v1i5kwv9xrkjiuiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdst7v1i5kwv9xrkjiuiw.png" alt="Got Fooled By Computer Screen. LOL!" width="800" height="1731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/amrit-dash/MLH-touch-grass" rel="noopener noreferrer"&gt;https://github.com/amrit-dash/MLH-touch-grass&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Gets People Outside
&lt;/h2&gt;

&lt;p&gt;Every "go outside" app I've tried sends a notification. I swipe it away in under a second.&lt;/p&gt;

&lt;p&gt;This one sits on the command I run 20 times a day. I can't swipe it away. The only way past it (well, the honest way) is the front door.&lt;/p&gt;

&lt;p&gt;And it's tiny. You don't need a hike. You need sky or a plant in frame, taken in the last 15 minutes. That's a 2 minute walk, which is exactly the break I wasn't taking.&lt;/p&gt;

&lt;p&gt;I built it, set it up, and promptly forgot about it. Then a push got blocked, and when I looked up from the screen the sun was setting outside.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;It's about 700 lines of Python plus one HTML page, with zero required dependencies.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The hook.&lt;/strong&gt; &lt;code&gt;touchgrass install&lt;/code&gt; writes a &lt;code&gt;pre-push&lt;/code&gt; hook. It compares now against the last time a photo passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The phone page.&lt;/strong&gt; &lt;code&gt;touchgrass prove&lt;/code&gt; starts a small server on your Wi-Fi with a one-time token in the URL. The page opens the rear camera directly (&lt;code&gt;capture="environment"&lt;/code&gt;), shrinks the photo to 1024px and sends it with its capture time. Not on the same Wi-Fi? &lt;code&gt;touchgrass prove --tunnel&lt;/code&gt; gives the page a temporary HTTPS address through a Cloudflare quick tunnel, so it works on mobile data too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The check.&lt;/strong&gt; The photo goes to Gemma through Ollama, on &lt;code&gt;localhost&lt;/code&gt;. The prompt asks for JSON only: &lt;code&gt;outdoors&lt;/code&gt;, &lt;code&gt;sky_or_plants&lt;/code&gt;, &lt;code&gt;photo_of_screen&lt;/code&gt;, a one line description and a roast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The verdict.&lt;/strong&gt; Plain Python decides, not the model. Outdoors, plus sky or plants, plus not a screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The double check.&lt;/strong&gt; Anything that passes gets one more question about screens (more on that below). Then the clock resets and you get a "grass receipt" (&lt;code&gt;touchgrass log&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I kept the decision in code on purpose. The model describes the photo, and 3 lines of &lt;code&gt;if&lt;/code&gt; decide what that means. Easier to test, and the model can't be talked into saying "pass".&lt;/p&gt;

&lt;h3&gt;
  
  
  Can you cheat it?
&lt;/h3&gt;

&lt;p&gt;Obviously that was the first thing I tried.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cheat&lt;/th&gt;
&lt;th&gt;What stops it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Old photo from the gallery&lt;/td&gt;
&lt;td&gt;Capture time has to be within 15 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Photo out the window&lt;/td&gt;
&lt;td&gt;Prompt says "not through a window" (I haven't tested this one yet)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grass wallpaper on your monitor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;photo_of_screen&lt;/code&gt;, plus the bezel double check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A monitor so close it fills the whole frame&lt;/td&gt;
&lt;td&gt;Nothing, yet. Keep reading.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git push --no-verify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Nothing. You'll know.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I also benchmarked it so I wasn't just trusting vibes. 18 labelled photos: 8 real outdoor scenes, 8 indoor rooms and desks, and 2 fakes where I pasted a field of cows onto a monitor in a desk photo.&lt;/p&gt;

&lt;p&gt;The first run got &lt;strong&gt;16 out of 18&lt;/strong&gt;. Every real photo was right. Both fakes sailed through as "Grass confirmed", with the roast "Hope the grass is as green as your commit history!" (rude, and also wrong).&lt;/p&gt;

&lt;p&gt;So I changed 2 things. The prompt now makes the model look for a screen &lt;em&gt;before&lt;/em&gt; it looks at the scene. And any photo that passes gets asked one more, much narrower question: what's at the edges of this photo, is it a bezel? Small models are bad at noticing a monitor frame when they're busy describing cows. They're good at it when that's the only question.&lt;/p&gt;

&lt;p&gt;Second run: &lt;strong&gt;18 out of 18&lt;/strong&gt;, about 26 seconds a photo on a 4 core CPU with no GPU. Full results are in &lt;a href="https://github.com/amrit-dash/MLH-touch-grass/blob/main/docs/benchmark.md" rel="noopener noreferrer"&gt;docs/benchmark.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(18 photos is tiny, and I fixed the prompt after seeing the fakes fail, so read that as "known holes closed", not "perfect". Also it calls the cows horses every single time.)&lt;/p&gt;

&lt;p&gt;Then I tried it for real, with my own phone.&lt;/p&gt;

&lt;p&gt;The first real photo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ Grass confirmed. A multi-story residential building with laundry hanging out to dry is visible under a blue sky.&lt;br&gt;
"It looks like your deployment is stuck waiting for the laundry cycle to finish."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fair.&lt;/p&gt;

&lt;p&gt;Then I put a photo of a grass field on my monitor and held my phone close enough that the grass filled the whole frame. No bezel, no desk.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ Grass confirmed. A vibrant green field of tall grass stretches towards a line of trees under a cloudy sky.&lt;br&gt;
"Hope your code is as lush and green as this field, or you'll be debugging in the weeds."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It got me. Both checks missed it.&lt;/p&gt;

&lt;p&gt;When I tried again and a sliver of the browser bar crept into the corner, it caught me straight away:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ That's a picture of a screen. Nice try.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I retook the full-frame shot, saved it and tried 4 different screen prompts on it, with and without Gemma's thinking turned on. Every one said it was a real field. Once the screen fills the frame there isn't much left to see, just slightly too-green grass.&lt;/p&gt;

&lt;p&gt;So that's the known hole. If you're willing to press your phone against your monitor to dodge a 2 minute walk, you win. You'll know, though.&lt;/p&gt;

&lt;p&gt;And the hillside in the demo above went through the new HTTPS tunnel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Matters Here
&lt;/h2&gt;

&lt;p&gt;This thing looks at photos of where you are. Your street, your backyard, maybe your face.&lt;/p&gt;

&lt;p&gt;I don't want that going to someone else's API every time I push code. With an open-weight model the only thing that ever looks at it is my laptop. (With &lt;code&gt;--tunnel&lt;/code&gt; the photo passes through Cloudflare on the way, encrypted, but nobody else's model sees it and nothing stores it.) It also costs nothing per check, works offline, and if you think my rules are too strict you can change the prompt or swap the model in one env var.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Missing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--no-verify&lt;/code&gt; skips it. It's a nudge with jokes, not a prison.&lt;/li&gt;
&lt;li&gt;The capture time comes from the phone's browser, so a determined cheater can fake it.&lt;/li&gt;
&lt;li&gt;A screen held close enough to fill the whole frame gets through. I couldn't prompt my way out of that one.&lt;/li&gt;
&lt;li&gt;Same Wi-Fi by default. &lt;code&gt;--tunnel&lt;/code&gt; fixes that, but then the photo goes through Cloudflare.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Best Use of Gemma&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
