<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Learning | 2i2c</title><link>https://deploy-preview-608--2i2c-org.netlify.app/tag/learning/</link><atom:link href="https://deploy-preview-608--2i2c-org.netlify.app/tag/learning/index.xml" rel="self" type="application/rss+xml"/><description>Learning</description><generator>Hugo Blox Builder (https://hugoblox.com)</generator><language>en-us</language><lastBuildDate>Tue, 20 Jan 2026 00:00:00 +0000</lastBuildDate><image><url>https://deploy-preview-608--2i2c-org.netlify.app/media/sharing.png</url><title>Learning</title><link>https://deploy-preview-608--2i2c-org.netlify.app/tag/learning/</link></image><item><title>Enabling CloudBank to safely manage their own cluster infrastructure</title><link>https://deploy-preview-608--2i2c-org.netlify.app/blog/cloudbank-self-service/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><guid>https://deploy-preview-608--2i2c-org.netlify.app/blog/cloudbank-self-service/</guid><description>&lt;p>We recently enabled
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/cloudbank/" >CloudBank&lt;/a> to run Terraform changes for their cluster without needing to wait on 2i2c engineers for each request. They run 50+ hubs for various community colleges, and we want to enable them to self serve as much of that as possible. When we introduced home directory quotas, they were no longer able to set up hubs by themselves without help from 2i2c engineers. Our goal was to empower them to be able to set up new hubs in a safe way while still benefiting from the home directory limits work.&lt;/p>
&lt;figure id="figure-cloudbank-simplifies-cloud-access-for-computer-science-research-and-education">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="CloudBank simplifies cloud access for computer science research and education." srcset="
/blog/cloudbank-self-service/featured_hu47b0024f802a2569dc8459bb45285f77_14544_3e2af71d895a3af46826ba1d224a2bf2.webp 400w,
/blog/cloudbank-self-service/featured_hu47b0024f802a2569dc8459bb45285f77_14544_d054f36eb6161bf5a999ff8a409ac162.webp 760w,
/blog/cloudbank-self-service/featured_hu47b0024f802a2569dc8459bb45285f77_14544_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://deploy-preview-608--2i2c-org.netlify.app/blog/cloudbank-self-service/featured_hu47b0024f802a2569dc8459bb45285f77_14544_3e2af71d895a3af46826ba1d224a2bf2.webp"
width="411"
height="88"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption>
CloudBank simplifies cloud access for computer science research and education.
&lt;/figcaption>&lt;/figure>
&lt;p>To do this safely, we needed to avoid granting access to shared Terraform state that could impact other communities. Following
&lt;a href="https://github.com/2i2c-org/infrastructure/pull/6797#pullrequestreview-3246004031" target="_blank" rel="noopener" >Yuvi&amp;rsquo;s suggestion&lt;/a>, we migrated CloudBank&amp;rsquo;s Terraform state to CloudBank’s own GCP project so that infrastructure changes from the CloudBank team are isolated to their cluster only, making this safe to try. This unblocks CloudBank to run changes like &lt;code>terraform plan&lt;/code> and &lt;code>terraform apply&lt;/code> themselves, meaning that CloudBank can deploy and update a hub without 2i2c engineers in the loop.&lt;/p>
&lt;p>This is a good example of how we aim to balance &lt;strong>community autonomy&lt;/strong> with &lt;strong>infrastructure safety&lt;/strong>. CloudBank can now self-serve routine operations while our broader infrastructure remains protected.&lt;/p>
&lt;h2 id="learn-more">
Learn more
&lt;a class="header-anchor" href="#learn-more">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>
&lt;a href="https://github.com/2i2c-org/infrastructure/issues/6795" target="_blank" rel="noopener" >The infrastructure issue describing this work&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://github.com/2i2c-org/infrastructure/pull/7339" target="_blank" rel="noopener" >A hub deployed by CloudBank using this workflow&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="acknowledgements">
Acknowledgements
&lt;a class="header-anchor" href="#acknowledgements">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>Thanks to Sean Morris and the
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/cloudbank/" >CloudBank&lt;/a> team at
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/bids/" >UC Berkeley&lt;/a> for collaborating on this workflow.&lt;/li>
&lt;/ul></description></item><item><title>Yuvi on scaling maintainer intuition to facilitate PR review with PR triage boards</title><link>https://deploy-preview-608--2i2c-org.netlify.app/blog/pr-triage-boards/</link><pubDate>Wed, 19 Nov 2025 00:00:00 +0000</pubDate><guid>https://deploy-preview-608--2i2c-org.netlify.app/blog/pr-triage-boards/</guid><description>&lt;p>Yuvi has a recent post on the
&lt;a href="https://jupyter.org" target="_blank" rel="noopener" >Jupyter blog&lt;/a> on how his &amp;ldquo;maintainer intuition&amp;rdquo; about reviewable pull requests grew into the open-source
&lt;a href="https://github.com/jupyter/pr-triage-board-bot" target="_blank" rel="noopener" >&lt;code>pr-triage-board-bot&lt;/code>&lt;/a>, a reusable workflow that keeps GitHub Project boards curated for the JupyterHub, JupyterLab, and GeoJupyter communities.
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/blog/pr-triage-boards/../foundational-contributions/" >Foundational contributions&lt;/a> are rellay important to 2i2c. This is a great example of building clever technical systems that help maintainers prioritize the social work of facilitating contributions to keep ecosystems healthy.&lt;/p>
&lt;figure id="figure-the-jupyterhub-pr-triage-boardhttpsgithubcomorgsjupyterhubprojects4views9">
&lt;div class="d-flex justify-content-center">
&lt;div class="w-100" >&lt;img alt="The [JupyterHub PR Triage board](https://github.com/orgs/jupyterhub/projects/4/views/9)." srcset="
/blog/pr-triage-boards/featured_hu42e3292f220b90b3776c7d0485ebe6f6_578487_75fe5f71a89f9494562e889e6ed95a90.webp 400w,
/blog/pr-triage-boards/featured_hu42e3292f220b90b3776c7d0485ebe6f6_578487_9e48a041e2580bd24fd8576d6b78b8e3.webp 760w,
/blog/pr-triage-boards/featured_hu42e3292f220b90b3776c7d0485ebe6f6_578487_1200x1200_fit_q75_h2_lanczos_3.webp 1200w"
src="https://deploy-preview-608--2i2c-org.netlify.app/blog/pr-triage-boards/featured_hu42e3292f220b90b3776c7d0485ebe6f6_578487_75fe5f71a89f9494562e889e6ed95a90.webp"
width="760"
height="367"
loading="lazy" data-zoomable />&lt;/div>
&lt;/div>&lt;figcaption>
The
&lt;a href="https://github.com/orgs/jupyterhub/projects/4/views/9" target="_blank" rel="noopener" >JupyterHub PR Triage board&lt;/a>.
&lt;/figcaption>&lt;/figure>
&lt;p>Our favorite quote shares the vibe and cultural principles that drive this effort:&lt;/p>
&lt;blockquote class="pull-quote">
&lt;p>If the author of the PR is a newish contributor, I want to encourage them to stick around by being responsive to their gift. All PRs are gifts that we may or may not choose to accept, but should do so with grace.&lt;/p>
&lt;cite>&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/author/yuvaraj-yuvi/" >Yuvi&lt;/a>&lt;/cite>
&lt;/blockquote>
&lt;p>Yuvi frames the problem here:&lt;/p>
&lt;blockquote>
&lt;p>Reviewing PRs is a critical way that maintainers keep an open source project moving forward, but identifying PRs that can productively be merged is hard.&lt;/p>
&lt;/blockquote>
&lt;p>And notes that &lt;em>human scalability&lt;/em> is often a big bottleneck:&lt;/p>
&lt;blockquote>
&lt;p>One key bottleneck we identified in the process was Step 2. In particular, I was relying on my maintainer intuition to pick a single PR that I believe can be merged, so others in the team can do review work. I started exploring what this intuition is, and if it can be scaled.&lt;/p>
&lt;/blockquote>
&lt;p>Here&amp;rsquo;s his list of &amp;ldquo;intuitions&amp;rdquo; that he uses to choose PRs to work on:&lt;/p>
&lt;blockquote>
&lt;ol>
&lt;li>PRs that aren’t too big, and are a reasonable size that can be merged within a 2 week window&lt;/li>
&lt;li>CI tests passing, so at least our automated checks haven’t caught any issues with it
Features or bug fixes that I believe add value to the project and move us in the right direction towards being able to support our users as they need (this is the hardest!)&lt;/li>
&lt;li>If the author of the PR is a newish contributor, as I want to encourage them to stick around by being responsive to their gift. All PRs are gifts that we may or may not choose to accept, but should do so with grace.&lt;/li>
&lt;li>How long ago the PR was opened. There is such a big difference between a response to your PR 2 days after you make it vs 2 months vs 2 years. I prioritized newer PRs.&lt;/li>
&lt;li>What kind of contribution is it primarily? Different engineers on our team have different skillsets (JS, Python, etc) and I wanted to match the PR to what the engineer preferred code reviewing.&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;p>And the board essentially tries to capture many of these intuitions by signal-boosting them in one place:&lt;/p>
&lt;blockquote>
&lt;p>we can roughly say ‘Pick a PR that looks good to you from the top of the “First Time Contributor” or “Seasoned Contributor” list’, and that relieves me from being the bottleneck quite a bit.&lt;/p>
&lt;/blockquote>
&lt;p>Read more in the original article:
&lt;a href="https://blog.jupyter.org/scaling-maintainer-intuition-with-pull-request-triage-boards-779f2387498b" target="_blank" rel="noopener" >Scaling &amp;ldquo;Maintainer Intuition&amp;rdquo; with Pull Request Triage Boards&lt;/a>.&lt;/p>
&lt;h2 id="acknowledgements">
Acknowledgements
&lt;a class="header-anchor" href="#acknowledgements">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/jupyter/" >Project Jupyter&lt;/a> for trusting us to incubate and now donate the bot code to the broader ecosystem.&lt;/li>
&lt;li>
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/jupyterhub/" >JupyterHub&lt;/a> and
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/geojupyter/" >GeoJupyter&lt;/a> contributors who tested the triage views and fed real maintainer workflows back into the design.&lt;/li>
&lt;li>
&lt;a href="https://github.com/jasongrout" target="_blank" rel="noopener" >Jason Grout&lt;/a>,
&lt;a href="https://github.com/rgaiacs" target="_blank" rel="noopener" >Raniere Silva&lt;/a>, and
&lt;a href="https://github.com/mfisher87" target="_blank" rel="noopener" >Matt Fisher&lt;/a> for spotting the experiment early and helping it land across multiple orgs.&lt;/li>
&lt;/ul></description></item><item><title>Creating a re-usable redirect generator for Jupyter Book 1 migrations</title><link>https://deploy-preview-608--2i2c-org.netlify.app/blog/jb1-redirect-generator/</link><pubDate>Wed, 12 Nov 2025 00:00:00 +0000</pubDate><guid>https://deploy-preview-608--2i2c-org.netlify.app/blog/jb1-redirect-generator/</guid><description>&lt;p>When migrating documentation from
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/jupyter-book/" >Jupyter Book&lt;/a> 1 to Jupyter Book 2, URL structures change dramatically and break external links. We spent some time createing a re-usable tool to solve this problem across multiple projects.&lt;/p>
&lt;p>You can check out the tool below:&lt;/p>
&lt;p>
&lt;a href="https://github.com/jupyter-book/jb1-redirect-generator" target="_blank" rel="noopener" >&lt;i class='fa-brands fa-github'>&lt;/i> jupyter-book/jb1-redirect-generator&lt;/a>&lt;/p>
&lt;p>It&amp;rsquo;s designed to be run in a self-contained way by putting the dependencies in &lt;code>script&lt;/code> metadata at the top.
This means you can run it like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">uv run https://raw.githubusercontent.com/jupyter-book/jb1-redirect-generator/main/generate_redirects.py
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This let&amp;rsquo;s you generate redirects from JB1 -&amp;gt; JB2 URL structures and dump them in a &lt;code>_build/html&lt;/code> folder with your JB2 built pages.&lt;/p>
&lt;p>We tested this out by converting the
&lt;a href="https://jupyter.org/governance" target="_blank" rel="noopener" >Jupyter Governance docs&lt;/a> to Jupyter Book 2 and running it there.
You can find a noxfile that runs these commands here:&lt;/p>
&lt;p>
&lt;a href="https://github.com/jupyter/governance/blob/bcdae30efdecbe75bc4751ef1fe1e602fe82ee10/noxfile.py#L25-L37" target="_blank" rel="noopener" >&lt;i class='fa-brands fa-github'>&lt;/i> jupyter/governance/blob/bcdae30efdecbe75bc4751ef1fe1e602fe82ee10/noxfile.py#L25-L37&lt;/a>&lt;/p>
&lt;p>And its use in a GitHub Workflow here:&lt;/p>
&lt;p>
&lt;a href="https://github.com/jupyter/governance/blob/bcdae30efdecbe75bc4751ef1fe1e602fe82ee10/.github/workflows/deploy.yml#L39-L44" target="_blank" rel="noopener" >&lt;i class='fa-brands fa-github'>&lt;/i> jupyter/governance/blob/bcdae30efdecbe75bc4751ef1fe1e602fe82ee10/.github/workflows/deploy.yml#L39-L44&lt;/a>&lt;/p>
&lt;h2 id="learn-more">
Learn more
&lt;a class="header-anchor" href="#learn-more">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>
&lt;a href="https://github.com/jupyter-book/jb1-redirect-generator" target="_blank" rel="noopener" >jb1-redirect-generator repository&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://github.com/jupyter/governance/pull/307" target="_blank" rel="noopener" >Jupyter governance PR using the new script&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Community learning: Hub config to pass oauth tokens into user environments</title><link>https://deploy-preview-608--2i2c-org.netlify.app/blog/communities-learning-from-one-another/</link><pubDate>Thu, 06 Nov 2025 00:00:00 +0000</pubDate><guid>https://deploy-preview-608--2i2c-org.netlify.app/blog/communities-learning-from-one-another/</guid><description>&lt;p>One of our favorite things to see: communities learning from and building on each other&amp;rsquo;s work!&lt;/p>
&lt;p>
&lt;a href="https://ops.maap-project.org/" target="_blank" rel="noopener" >MAAP&lt;/a> recently
&lt;a href="https://github.com/2i2c-org/infrastructure/pull/7068" target="_blank" rel="noopener" >contributed infrastructure configuration&lt;/a> inspired by
&lt;a href="https://github.com/2i2c-org/infrastructure/blob/0046e14a68d7af9e353c494ee6ad39beb0ce970a/config/clusters/earthscope/common.values.yaml#L29" target="_blank" rel="noopener" >EarthScope&amp;rsquo;s approach&lt;/a> to handling authentication tokens. Both communities need to pass OAuth tokens into user environments so their SDKs can access protected data - and MAAP adapted EarthScope&amp;rsquo;s pattern to fit their needs.&lt;/p>
&lt;p>This is the kind of peer-to-peer knowledge sharing we hope to foster with our
&lt;a href="https://github.com/2i2c-org/infrastructure" target="_blank" rel="noopener" >open infrastructure model&lt;/a>. When infrastructure is open and communities can see each other&amp;rsquo;s solutions, they can adapt and build on proven approaches rather than starting from scratch.&lt;/p>
&lt;h2 id="learn-more">
Learn more
&lt;a class="header-anchor" href="#learn-more">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>
&lt;a href="https://github.com/2i2c-org/infrastructure/pull/7068" target="_blank" rel="noopener" >MAAP&amp;rsquo;s PR&lt;/a> adapting the configuration&lt;/li>
&lt;li>
&lt;a href="https://github.com/2i2c-org/infrastructure/blob/0046e14a68d7af9e353c494ee6ad39beb0ce970a/config/clusters/earthscope/common.values.yaml#L29" target="_blank" rel="noopener" >EarthScope&amp;rsquo;s original config&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://github.com/2i2c-org/infrastructure" target="_blank" rel="noopener" >Our infrastructure repository&lt;/a> where all community configurations live&lt;/li>
&lt;/ul>
&lt;h2 id="acknowledgments">
Acknowledgments
&lt;a class="header-anchor" href="#acknowledgments">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>
&lt;a href="https://ops.maap-project.org/" target="_blank" rel="noopener" >MAAP team&lt;/a> for adapting and contributing this configuration&lt;/li>
&lt;li>
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/earthscope/" >EarthScope Consortium&lt;/a> for the original implementation&lt;/li>
&lt;/ul></description></item><item><title>Refactoring Jupyter Book 2 documentation ahead of a major release</title><link>https://deploy-preview-608--2i2c-org.netlify.app/blog/jupyter-book-docs-refactor/</link><pubDate>Sat, 01 Nov 2025 00:00:00 +0000</pubDate><guid>https://deploy-preview-608--2i2c-org.netlify.app/blog/jupyter-book-docs-refactor/</guid><description>&lt;p>Documentation is what turns open source code into products that people actually want to use. We recently spent a few days refactoring the
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/jupyter-book/" >Jupyter Book&lt;/a> documentation to prepare for the upcoming Jupyter Book 2 release, and we&amp;rsquo;re excited about how much clearer the docs have become!&lt;/p>
&lt;h2 id="what-we-did">
What we did
&lt;a class="header-anchor" href="#what-we-did">#&lt;/a>
&lt;/h2>&lt;p>We restructured the docs using the
&lt;a href="https://diataxis.fr/" target="_blank" rel="noopener" >Diataxis framework&lt;/a> to better organize content by user type and task:&lt;/p>
&lt;ul>
&lt;li>Reorganized into clear topic areas with landing pages for easier navigation&lt;/li>
&lt;li>Added missing content like the feature voting table and contributing guides&lt;/li>
&lt;li>Created upgrade guidance to help users understand the relationship between Jupyter Book 2 and MyST&lt;/li>
&lt;/ul>
&lt;p>This work helps users find what they need faster and gives the project a stronger foundation to build on going forward.&lt;/p>
&lt;h2 id="why-were-excited-about-it">
Why we&amp;rsquo;re excited about it
&lt;a class="header-anchor" href="#why-were-excited-about-it">#&lt;/a>
&lt;/h2>&lt;p>Better documentation reduces maintainer burden by helping users answer their own questions, and it makes the project more welcoming and useful to new contributors. We hope this makes Jupyter Book more accessible to everyone and lays a good foundation for the new release!&lt;/p>
&lt;p>We&amp;rsquo;re also excited because so many others helped provide edits and comments!&lt;/p>
&lt;h2 id="learn-more">
Learn more
&lt;a class="header-anchor" href="#learn-more">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>
&lt;a href="https://github.com/jupyter-book/jupyter-book/pull/2422" target="_blank" rel="noopener" >PR implementing the refactor&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://github.com/jupyter-book/jupyter-book/blob/next/docs/contribute/docs.md" target="_blank" rel="noopener" >Documentation principles we developed&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://next.jupyterbook.org" target="_blank" rel="noopener" >Jupyter Book 2 documentation&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="acknowledgements">
Acknowledgements
&lt;a class="header-anchor" href="#acknowledgements">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>Thanks to
&lt;a href="https://github.com/rlanzafame" target="_blank" rel="noopener" >@rlanzafame&lt;/a>,
&lt;a href="https://github.com/FreekPols" target="_blank" rel="noopener" >@FreekPols&lt;/a>, and
&lt;a href="https://github.com/bsipocz" target="_blank" rel="noopener" >@bsipocz&lt;/a> for their helpful reviews, edits, and feedback on the PR!&lt;/li>
&lt;li>
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/pythia/" >Project Pythia&lt;/a>,
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/cryocloud/" >CryoCloud&lt;/a>,
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/nasa-open-science/" >NASA Open Science / ScienceCore&lt;/a>, and the Berkeley educational projects are our primary member communities using MyST and
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/jupyter-book/" >Jupyter Book&lt;/a>. Their support covers the cost of these kinds of foundational contributions.&lt;/li>
&lt;/ul></description></item><item><title>Communities learning from one another - Project Pythia and ICESat-2 Hackweeks</title><link>https://deploy-preview-608--2i2c-org.netlify.app/blog/pythia-icesat2-synergy/</link><pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate><guid>https://deploy-preview-608--2i2c-org.netlify.app/blog/pythia-icesat2-synergy/</guid><description>&lt;p>We wanted to share a short vignette about two of our communities learning from one another.&lt;/p>
&lt;p>At the latest
&lt;a href="https://docs.google.com/document/d/e/2PACX-1vQWQrgHs_G5XyNH5GTFYydH_woUZcyZibdxPUWLpqFUYs20WM93kdx5onwOaizC_3-tfnbreMNQbYAp/pub" target="_blank" rel="noopener" >Project Pythia community meeting&lt;/a>,
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/pythia/" >Project Pythia&lt;/a> met with representatives from
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/cryocloud/" >ICESat-2&lt;/a> to share learning about notebooks and cookbooks in educational settings.&lt;/p>
&lt;p>Anthony Arendt from UW&amp;rsquo;s eScience Institute shared how they&amp;rsquo;ve used educational notebooks in their hackweek programs. The discussion explored ways to improve cookbooks, especially for large collections that require different computational environments, sparking ideas about higher-level abstractions for organizing educational content. There is a lot of overlap in the needs and workflows of these communities, and we&amp;rsquo;re hopeful they can find ways to re-use one another&amp;rsquo;s ideas, content, and infrastructure.&lt;/p>
&lt;p>One of our service goals is to make it easier for our member communities to &lt;em>learn from one another&lt;/em> - using standardized tools and infrastructure means we can learn what works, what doesn&amp;rsquo;t, and collectively improve our workflows more quickly. We&amp;rsquo;re working on ways to encourage this kind of interaction in our member networks, so we wanted to celebrate this little win.&lt;/p>
&lt;h2 id="learn-more-about-these-communities">
Learn more about these communities
&lt;a class="header-anchor" href="#learn-more-about-these-communities">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>
&lt;a href="https://deploy-preview-608--2i2c-org.netlify.app/collaborators/pythia/" >Project Pythia&lt;/a> - An educational resource for geoscience computing with open-source Python&lt;/li>
&lt;li>
&lt;a href="https://cookbooks.projectpythia.org/" target="_blank" rel="noopener" >Project Pythia Cookbooks&lt;/a> - Domain-specific example workflows for geoscience&lt;/li>
&lt;li>
&lt;a href="https://icesat-2.hackweek.io/" target="_blank" rel="noopener" >ICESat-2 Hackweeks&lt;/a> - Collaborative learning events combining tutorials, peer learning, and team projects&lt;/li>
&lt;/ul></description></item><item><title>TIL: GitHub Action secrets are only available from non-forked repositories</title><link>https://deploy-preview-608--2i2c-org.netlify.app/blog/github-action-secrets-forked-repositories/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>https://deploy-preview-608--2i2c-org.netlify.app/blog/github-action-secrets-forked-repositories/</guid><description>&lt;p>If you&amp;rsquo;ve worked with GitHub Actions in open source projects, you might encounter a hard-to-debug error where repository secrets are simply &lt;em>empty&lt;/em>. That&amp;rsquo;s probably because the PR is from a forked repository! Here&amp;rsquo;s a little learning we had after losing a bunch of time figuring this out:&lt;/p>
&lt;h2 id="our-pr-from-a-fork-was-using-empty-strings-for-repository-secrets">
Our PR from a fork was using empty strings for repository secrets
&lt;a class="header-anchor" href="#our-pr-from-a-fork-was-using-empty-strings-for-repository-secrets">#&lt;/a>
&lt;/h2>&lt;p>
&lt;a href="https://github.com/executablebooks/github-activity" target="_blank" rel="noopener" >&lt;code>github-activity&lt;/code>&lt;/a> is a tool we help maintain for
&lt;a href="https://github-activity.readthedocs.io/en/latest/#how-we-define-contributors-in-the-reports" target="_blank" rel="noopener" >generating changelogs from a wider variety of contributions&lt;/a> than GitHub&amp;rsquo;s defaults. We needed to set a secret to raise the API rate limits for the tool&amp;rsquo;s tests. These tests pull data from repositories outside of the &lt;code>github-activity&lt;/code> repository itself, which quickly hit GitHub&amp;rsquo;s rate limits without authentication.&lt;/p>
&lt;p>The problem seemed straightforward at first: we added the secret, but the workflow was acting as if the secret didn&amp;rsquo;t exist at all. After updating the code to explicitly check for empty strings, we discovered the authentication token was actually &lt;em>an empty string&lt;/em>, even though it had been set.&lt;/p>
&lt;p>After some debugging, we uncovered the root cause: &lt;strong>the PR was opened from a fork of &lt;code>github-activity&lt;/code>&lt;/strong>. GitHub intentionally
&lt;a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets" target="_blank" rel="noopener" >makes secrets appear as empty strings when a PR originates from a forked repository&lt;/a>. This is a security measure to prevent unauthorized access to sensitive credentials.&lt;/p>
&lt;h2 id="a-quick-fix-re-open-the-pr-from-the-base-repository">
A quick fix: re-open the PR from the base repository
&lt;a class="header-anchor" href="#a-quick-fix-re-open-the-pr-from-the-base-repository">#&lt;/a>
&lt;/h2>&lt;p>The immediate fix was simple: re-open the PR from a branch in the base repository rather than from the fork. This worked perfectly, but it&amp;rsquo;s not a sustainable solution for open source projects that rely on community contributions from forks.
We don&amp;rsquo;t want to create a dynamic where maintainers have different PR workflows because they&amp;rsquo;re operating on the base repository.&lt;/p>
&lt;h2 id="how-use-secrets-with-forked-repositories-in-a-safe-ish-way">
How use secrets with forked repositories in a safe-ish way
&lt;a class="header-anchor" href="#how-use-secrets-with-forked-repositories-in-a-safe-ish-way">#&lt;/a>
&lt;/h2>&lt;p>If you need to make secrets available to PRs from forks, there are a few approaches we learned about, each with security trade-offs:&lt;/p>
&lt;h3 id="the-pull_request_target-workflow">
The &lt;code>pull_request_target&lt;/code> workflow
&lt;a class="header-anchor" href="#the-pull_request_target-workflow">#&lt;/a>
&lt;/h3>&lt;p>GitHub provides a
&lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#controlling-changes-from-forks-to-workflows-in-public-repositories" target="_blank" rel="noopener" >&lt;code>pull_request_target&lt;/code> workflow&lt;/a> that can access secrets even when triggered by forked PRs. In this case, GitHub will &lt;strong>always run the test suite on &lt;code>main&lt;/code>&lt;/strong>, instead of any changes your PR introduces.&lt;/p>
&lt;p>&lt;strong>Why this is dangerous&lt;/strong>: malicious actors could add &lt;em>code&lt;/em> to a PR that exfiltrates your secrets (for example, Python code that prints &lt;code>os.environ[&amp;quot;MY_SECRET&amp;quot;]&lt;/code>).&lt;/p>
&lt;p>As a result, &lt;strong>only use secrets that you&amp;rsquo;re OK with being public&lt;/strong>. In this case, we generated a read-only token with restricted permissions. However, this is still kinda risky so use at your own peril.&lt;/p>
&lt;p>If your repository workflows &lt;em>require&lt;/em> a secret that &lt;em>absolutely cannot be public&lt;/em> (e.g., a publishing key for a package repository), try a method like the following:&lt;/p>
&lt;h3 id="using-github-environments-for-granular-control">
Using GitHub Environments for granular control
&lt;a class="header-anchor" href="#using-github-environments-for-granular-control">#&lt;/a>
&lt;/h3>&lt;p>A safer approach is to use
&lt;a href="https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments" target="_blank" rel="noopener" >GitHub Environments&lt;/a>, which let you restrict which secrets are available to specific jobs. This way, you can ensure that only non-critical secrets (like those needed for testing) are available to jobs that run on forked PRs, while keeping sensitive secrets (like PyPI publishing tokens) restricted to trusted contexts.&lt;/p>
&lt;p>This is the approach we implemented in &lt;code>github-activity&lt;/code>, and it provides a good balance between security and community contribution workflows. We created a separate environment for publishing to PyPI so that its secret is never available to the job that runs with &lt;code>pull_request_target&lt;/code>.&lt;/p>
&lt;h2 id="we-hope-this-saves-you-time">
We hope this saves you time!
&lt;a class="header-anchor" href="#we-hope-this-saves-you-time">#&lt;/a>
&lt;/h2>&lt;p>Hopefully this learning is useful to others who run into the same confusing behavior. We&amp;rsquo;ve added a few improvements to &lt;code>github-activity&lt;/code> to more reliably check for empty strings to surface this kind of condition, but knowing the basic behavior of GitHub environments and forked PRs is even better.&lt;/p>
&lt;h2 id="learn-more">
Learn more
&lt;a class="header-anchor" href="#learn-more">#&lt;/a>
&lt;/h2>&lt;ul>
&lt;li>
&lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#controlling-changes-from-forks-to-workflows-in-public-repositories" target="_blank" rel="noopener" >GitHub documentation on controlling changes from forks to workflows&lt;/a>&lt;/li>
&lt;li>
&lt;a href="https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments" target="_blank" rel="noopener" >GitHub Environments for deployment&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>