

<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>電腦相關應用 &#8211; Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/category/computer/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Wed, 15 Apr 2026 10:01:46 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://stackoverflow.max-everyday.com/wp-content/uploads/2017/02/max-stackoverflow-256.png</url>
	<title>電腦相關應用 &#8211; Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Azure Container Apps (ACA) 存取 VNet 內的資源</title>
		<link>https://stackoverflow.max-everyday.com/2026/04/azure-container-apps-vnet/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/04/azure-container-apps-vnet/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 15 Apr 2026 10:01:45 +0000</pubDate>
				<category><![CDATA[Azure 筆記]]></category>
		<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[azure]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8293</guid>

					<description><![CDATA[既然你已經從 Web App 遷移到 Azure...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="559" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/azure-container-apps-vnet_clean-1024x559.jpg?v=1776247294" alt="" class="wp-image-8295" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/azure-container-apps-vnet_clean-1024x559.jpg?v=1776247294 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/azure-container-apps-vnet_clean-600x327.jpg?v=1776247294 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/azure-container-apps-vnet_clean-768x419.jpg?v=1776247294 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/azure-container-apps-vnet_clean.jpg?v=1776247294 1408w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>既然你已經從 Web App 遷移到 <strong>Azure Container Apps (ACA)</strong>，網路架構的邏輯會稍微有些不同。ACA 本身就是建立在一個名為 <strong>Container Apps Environment</strong> 的容器環境中，因此 VNet 的整合是在「環境 (Environment)」層級設定的，而不是單獨在特定的 App 上。</p>



<p>存取 VNet 資源的流程主要分為：<strong>環境網路配置</strong>、<strong>基礎架構委派</strong>、以及 <strong>ACA 部署</strong>。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">1. 核心觀念：VNet 整合模式</h3>



<p>ACA 支援兩種網路模式：</p>



<ul class="wp-block-list">
<li><strong>External (外部)</strong>：環境有公網 IP，但後端可以存取 VNet 資源。</li>



<li><strong>Internal (內部)</strong>：環境沒有公網 IP，所有存取都必須透過 VNet（適合純內網系統）。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">2. 實作流程與 CLI 指令</h3>



<h4 class="wp-block-heading">第一步：建立（或準備）Subnet 並設定委派</h4>



<p>與 Web App 相似，ACA 的 Subnet 也需要特定的委派，但它是委派給 <code>Microsoft.App/environments</code>。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 定義變數 (假設 VNet 在不同的 RG)
$VNET_RG = "rg-network-shared"
$VNET_NAME = "vnet-pr-stg-twn-01"
$ACA_SUBNET_NAME = "snet-aca-integration" # 建議建立一個新的 Subnet

# 更新 Subnet 委派
az network vnet subnet update `
  --resource-group $VNET_RG `
  --vnet-name $VNET_NAME `
  --name $ACA_SUBNET_NAME `
  --delegations Microsoft.App/environments
</code></pre>



<h4 class="wp-block-heading">第二步：取得 Subnet ID</h4>



<p>跨 RG 操作時，直接拿 ID 最安全：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code>$SUBNET_ID = $(az network vnet subnet show `
  --resource-group $VNET_RG `
  --vnet-name $VNET_NAME `
  --name $ACA_SUBNET_NAME `
  --query id -o tsv)
</code></pre>



<h4 class="wp-block-heading">第三步：建立 Container Apps Environment 並綁定 VNet</h4>



<p>這是關鍵步驟。一旦 Environment 建立並綁定 VNet，該環境下所有的 Container Apps 都能直接存取該 VNet 內的資源。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code>$ENVIRONMENT_NAME = "cae-pr-stg-twn-001"

az containerapp env create `
  --name $ENVIRONMENT_NAME `
  --resource-group $RESOURCE_GROUP `
  --location "taiwannorth" `
  --infrastructure-subnet-id $SUBNET_ID `
  --internal-client-ip-dns-mode "None"  # 如果需要外部存取設為 None，純內網設為 Internal
</code></pre>



<h4 class="wp-block-heading">第四步：部署 Container App 到該環境</h4>



<p>PowerShell</p>



<pre class="wp-block-code"><code>az containerapp create `
  --name "my-fast-app" `
  --resource-group $RESOURCE_GROUP `
  --environment $ENVIRONMENT_NAME `
  --image "${ACR_NAME}.azurecr.io/policy-app:v1" `
  --registry-server "${ACR_NAME}.azurecr.io" `
  --target-port 80 `
  --ingress 'external' # 允許外部流量進來
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">3. 如何存取 VNet 內的資源？</h3>



<p>部署完成後，你的 Container App 就像是在 VNet 內的一台機器：</p>



<ul class="wp-block-list">
<li><strong>透過 IP 存取</strong>：你可以直接 ping 或連線 VNet 內 VM 的私有 IP。</li>



<li><strong>透過 DNS 存取</strong>：如果你 VNet 內有設定 <strong>Azure Private DNS Zones</strong>，你需要確保 ACA 環境關聯了該 Private DNS，這樣才能解析例如 <code>mydb.privatelink.database.azure.com</code> 這樣的域名。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">4. 權限鬼打牆預警 (RBAC)</h3>



<p>因為你跨了 Resource Group，且不是 Owner：</p>



<ol start="1" class="wp-block-list">
<li><strong>Network Contributor</strong>：你的帳號必須在 <code>VNet RG</code> 擁有這個角色，否則無法將 ACA 環境掛載到該 Subnet。</li>



<li><strong>Managed Identity</strong>：如果你的 ACA 需要存取 Key Vault 或 SQL (存取權限控管)，建議幫 ACA 開啟 <code>System Assigned Identity</code>。</li>
</ol>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 總結</h3>



<p>ACA 的優勢在於<strong>一次設定，全家享用</strong>。只要把 <code>Environment</code> 塞進 VNet，之後在裡面建立的幾十個微服務就通通都能連內網了。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>寫一段建立新 Subnet 的指令</strong></p>



<p>既然要為 <strong>Azure Container Apps (ACA)</strong> 建立新的 Subnet，我們必須確保該 Subnet 的位址空間不與現有的資源衝突，並且正確設定 <strong>委派 (Delegation)</strong>。</p>



<p>在跨 Resource Group 的情境下，請執行以下 PowerShell 指令：</p>



<h3 class="wp-block-heading">1. 定義變數</h3>



<p>請根據你的實際環境修改位址範圍（例如 <code>10.0.2.0/24</code>）：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code>$VNET_RG = "rg-network-shared"         # VNet 所在的 RG
$VNET_NAME = "vnet-pr-stg-twn-01"       # 現有的 VNet 名稱
$NEW_SUBNET_NAME = "snet-aca-infra-01"  # 新 Subnet 的名字
$ADDRESS_PREFIX = "10.0.2.0/24"         # 確保此範圍在 VNet 內且未被使用
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">2. 建立 Subnet 並設定委派</h3>



<p>我們使用 <code>az network vnet subnet create</code> 指令。關鍵在於 <code>--delegations</code>，這會告訴 Azure：「這個網段是給 ACA 環境專用的」。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code>az network vnet subnet create `
  --resource-group $VNET_RG `
  --vnet-name $VNET_NAME `
  --name $NEW_SUBNET_NAME `
  --address-prefixes $ADDRESS_PREFIX `
  --delegations "Microsoft.App/environments"
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">3. 取得新 Subnet 的 ID (供後續建立 ACA 環境使用)</h3>



<p>建立完成後，建議直接把 ID 存起來，這樣在建立 ACA Environment 時才不會因為名稱解析問題而「鬼打牆」：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code>$ACA_SUBNET_ID = $(az network vnet subnet show `
  --resource-group $VNET_RG `
  --vnet-name $VNET_NAME `
  --name $NEW_SUBNET_NAME `
  --query id -o tsv)

# 驗證一下有沒有抓到
echo $ACA_SUBNET_ID
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 執行前的注意事項 (避免再次鬼打牆)</h3>



<ul class="wp-block-list">
<li><strong>位址衝突</strong>：請先確認 <code>$ADDRESS_PREFIX</code> 沒有被 VNet 內其他的 Subnet 佔用。你可以執行 <code>az network vnet show -g $VNET_RG -n $VNET_NAME --query "subnets[].addressPrefix"</code> 來查看已使用的網段。</li>



<li><strong>Subnet 大小</strong>：ACA 規定最小必須是 <strong>/23</strong> (512 個 IP) 或 <strong>/27</strong> (32 個 IP)，具體取決於你的工作負載。對於測試環境，建議至少使用 <strong><code>/24</code></strong> 或 <strong><code>/23</code></strong>，因為 ACA 在升級（Rolling update）或擴展時會消耗大量 IP。</li>



<li><strong>權限</strong>：雖然你不是 Owner，但只要你在 <code>$VNET_RG</code> 擁有 <strong>Network Contributor</strong> 角色，就可以成功執行這段指令。</li>
</ul>



<p>下一步你就可以拿著 <code>$ACA_SUBNET_ID</code> 去執行 <code>az containerapp env create</code> 了。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>準備建立環境的完整指令</p>



<p>沒問題！我們直接把 <strong>建立 Subnet</strong> 到 <strong>建立 ACA 環境</strong> 的完整流程串起來。這組指令會確保你的 Container Apps 基礎設施能夠正確與 VNet 銜接。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">第一階段：建立 Subnet (跨 RG)</h3>



<p>請確保你已經定義了之前討論的變數（<code>$VNET_RG</code>, <code>$VNET_NAME</code> 等）。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 1. 在指定的 VNet RG 下建立專屬 Subnet
az network vnet subnet create `
  --resource-group $VNET_RG `
  --vnet-name $VNET_NAME `
  --name $NEW_SUBNET_NAME `
  --address-prefixes $ADDRESS_PREFIX `
  --delegations "Microsoft.App/environments"

# 2. 獲取該 Subnet 的 Resource ID (這是建立環境的必要參數)
$ACA_SUBNET_ID = $(az network vnet subnet show `
  --resource-group $VNET_RG `
  --vnet-name $VNET_NAME `
  --name $NEW_SUBNET_NAME `
  --query id -o tsv)
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">第二階段：建立 ACA 環境 (Managed Environment)</h3>



<p>這是 ACA 的核心，「環境」決定了容器的網路邊界。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 定義環境名稱
$ENVIRONMENT_NAME = "cae-pr-stg-twn-001"

# 建立環境並掛載到 VNet ID
az containerapp env create `
  --name $ENVIRONMENT_NAME `
  --resource-group $RESOURCE_GROUP `
  --location "taiwannorth" `
  --infrastructure-subnet-id $ACA_SUBNET_ID `
  --internal-client-ip-dns-mode "None"
</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 專業提示：</strong></p>



<ul class="wp-block-list">
<li><code>--internal-client-ip-dns-mode "None"</code>：這代表你的 ACA 會有一個公用 IP (External)。如果你希望這是一個<strong>純內網</strong>（外面完全連不到）的環境，請將此參數改為 <code>Internal</code>。</li>
</ul>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">第三階段：部署第一個 Container App</h3>



<p>現在環境已經在 VNet 裡了，我們把你的 App 丟進去測試連通性。</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code>az containerapp create `
  --name "app-pr-stg-twn" `
  --resource-group $RESOURCE_GROUP `
  --environment $ENVIRONMENT_NAME `
  --image "${ACR_NAME}.azurecr.io/policy-app:v1" `
  --registry-server "${ACR_NAME}.azurecr.io" `
  --registry-username $ACR_USERNAME `
  --registry-password $ACR_PASSWORD `
  --target-port 80 `
  --ingress 'external' `
  --query "properties.configuration.ingress.fqdn"
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">網路架構圖解</h3>



<p>這套設定完成後，你的網路架構會長這樣：</p>



<h3 class="wp-block-heading">驗證 VNet 連通性</h3>



<p>部署成功後，你可以進入 ACA 的「主控台 (Console)」或是透過 <code>az containerapp exec</code> 嘗試 ping VNet 內的資源（例如資料庫的 Private IP）：</p>



<p>PowerShell</p>



<pre class="wp-block-code"><code># 進入容器內部測試
az containerapp exec --name "app-pr-stg-twn" --resource-group $RESOURCE_GROUP --command "ping 10.0.1.5"
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6d1.png" alt="🛑" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 避坑指南：關於「位置 (Location)」</h3>



<p>ACA 與 VNet 整合時，<strong>Region 必須完全一致</strong>。</p>



<ul class="wp-block-list">
<li>如果 VNet 在 <code>Japan East</code>，指令中的 <code>--location</code> 必須填 <code>japaneast</code>。</li>



<li>如果 VNet 在 <code>Taiwan North</code>，則填 <code>taiwannorth</code>。</li>
</ul>



<p>如果跨區建立，<code>az containerapp env create</code> 會報錯並告訴你 Subnet 不在該區域。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/04/azure-container-apps-vnet/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>在 fontforge 有3個點, 已選取為綠色狀態, 怎麼變成曲線?</title>
		<link>https://stackoverflow.max-everyday.com/2026/04/fontforge-make-arc/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/04/fontforge-make-arc/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 08 Apr 2026 15:05:28 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[FontForge]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8261</guid>

					<description><![CDATA[答案是, Menu -> Point -> Ma...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="627" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-08_22-38_n9-1024x627.jpg?v=1775660072" alt="" class="wp-image-8262" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-08_22-38_n9-1024x627.jpg?v=1775660072 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-08_22-38_n9-600x367.jpg?v=1775660072 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-08_22-38_n9-768x470.jpg?v=1775660072 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-08_22-38_n9.jpg?v=1775660072 1350w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>答案是, Menu -> Point -> Make Arc</p>



<figure class="wp-block-image size-full"><img decoding="async" width="311" height="449" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-08_22-55_nb.jpg?v=1775660669" alt="" class="wp-image-8263"/></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/04/fontforge-make-arc/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Sublime Text 移除行尾空白 &#8211; Trimmer</title>
		<link>https://stackoverflow.max-everyday.com/2026/04/sublime-text-trimmer/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/04/sublime-text-trimmer/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 08 Apr 2026 03:31:48 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8258</guid>

					<description><![CDATA[實際測試, 還是 Trimmer 用起來比較順手...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="909" height="772" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/sublime_text_2026-04-08-11-28-4q.jpg?v=1775619006" alt="" class="wp-image-8259" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/sublime_text_2026-04-08-11-28-4q.jpg?v=1775619006 909w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/sublime_text_2026-04-08-11-28-4q-600x510.jpg?v=1775619006 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/sublime_text_2026-04-08-11-28-4q-768x652.jpg?v=1775619006 768w" sizes="auto, (max-width: 909px) 100vw, 909px" /></figure>



<p>實際測試, 還是  Trimmer 用起來比較順手.</p>



<p>github: <a href="https://github.com/jonlabelle/Trimmer">https://github.com/jonlabelle/Trimmer</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>在 Sublime Text 中，除了內建的儲存自動刪除功能，如果你需要更強大的管理工具，像是即時高亮顯示或針對特定區域清理，以下是社群中最推薦的幾個外掛。</p>



<h3 class="wp-block-heading">TrailingSpaces</h3>



<p>這是最受歡迎的選擇。它的主要功能是會用醒目的顏色高亮顯示行尾多餘的空白，讓你一眼就能看到哪裡沒寫乾淨。</p>



<ol start="1" class="wp-block-list">
<li>安裝方式：透過 Package Control 搜尋 TrailingSpaces。</li>



<li>核心功能：除了高亮顯示，它提供 Delete Trailing Spaces 指令，可以一鍵清除全檔。</li>



<li>進階設定：你可以設定是否要在儲存時自動刪除，或是設定排除目前正在編輯的那一行，避免干擾輸入。</li>
</ol>



<h3 class="wp-block-heading">EditorConfig</h3>



<p>如果你是團隊開發，這是一個更好的方案。EditorConfig 並非專為刪除空白設計，但它是跨編輯器的格式規範工具。</p>



<ol start="1" class="wp-block-list">
<li>核心功能：透過專案根目錄的 .editorconfig 設定檔來強制規範。</li>



<li>設定語法：在設定檔中加入 trim_trailing_whitespace = true。</li>



<li>優點：只要專案成員都安裝了這個外掛，不論是用 Sublime、VS Code 還是 WebStorm，儲存時都會遵循相同的空白刪除邏輯，維持代碼的一致性。</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">如何挑選</h3>



<p>如果你只是想要視覺化看到哪些地方有空格，安裝 TrailingSpaces 就對了。如果你在公司工作，為了確保所有同事的格式都一樣，建議直接使用 EditorConfig。若只是單純想保持乾淨且不需要高亮，其實使用 Sublime 內建的 &#8220;trim_trailing_white_space_on_save&#8221;: true 設定就已經非常足夠。</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/04/sublime-text-trimmer/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>github CodeQL 支援 PHP 嗎?</title>
		<link>https://stackoverflow.max-everyday.com/2026/04/codeql-%e6%94%af%e6%8f%b4-php-%e5%97%8e/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/04/codeql-%e6%94%af%e6%8f%b4-php-%e5%97%8e/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 07 Apr 2026 00:11:51 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[php]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8246</guid>

					<description><![CDATA[error message: Error: Di...]]></description>
										<content:encoded><![CDATA[
<p>error message:</p>



<pre class="wp-block-preformatted">Error: Did not recognize the following languages: php<br>Warning: This run of the CodeQL Action does not have permission to access the CodeQL Action API endpoints. This could be because the Action is running on a pull request from a fork. If not, please ensure the workflow has at least the 'security-events: read' permission. Details: Code scanning is not enabled for this repository. Please enable code scanning in the repository settings. - https://docs.github.com/rest</pre>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="976" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/chrome_2026-04-07-08-04-4n-1024x976.jpg?v=1775520285" alt="" class="wp-image-8247" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/chrome_2026-04-07-08-04-4n-1024x976.jpg?v=1775520285 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/chrome_2026-04-07-08-04-4n-600x572.jpg?v=1775520285 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/chrome_2026-04-07-08-04-4n-768x732.jpg?v=1775520285 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/chrome_2026-04-07-08-04-4n-1536x1463.jpg?v=1775520285 1536w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/chrome_2026-04-07-08-04-4n.jpg?v=1775520285 1797w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>這看起來像是 CodeQL 分析時遇到了兩個常見的障礙：一個是語言設定問題，另一個是 GitHub API 的權限或設定問題。</p>



<p>以下是針對這兩項錯誤的解決方案：</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">1. 語言錯誤：<code>Did not recognize the following languages: php</code></h2>



<p><strong>問題點：</strong> 雖然 CodeQL 支援 PHP，但系統提示無法辨認。</p>



<p><strong>解決方法：</strong></p>



<ul class="wp-block-list">
<li><strong>檢查大小寫：</strong> 在 <code>.github/workflows/codeql.yml</code> 檔案中，請確保語言名稱是<strong>全小寫</strong>。</li>



<li><strong>更新 Action 版本：</strong> 確保你使用的是較新的版本（建議使用 <code>@v3</code>）。</li>



<li><strong>縮排與語法：</strong> 如果有多種語言，請使用列表格式。</li>
</ul>



<p>YAML</p>



<pre class="wp-block-code"><code># 範例設定
- name: Initialize CodeQL
  uses: github/codeql-action/init@v3
  with:
    languages: php  # 確保這裡是小寫
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">2. 權限與 API 錯誤：<code>does not have permission</code></h2>



<p><strong>問題點：</strong> 工作流（Workflow）沒有權限將掃描結果上傳到 GitHub 的安全後台。這通常發生在<strong>從 Fork（分叉）出來的 PR</strong>，或是權限設定過於嚴格時。</p>



<p><strong>解決方法：</strong></p>



<p>你需要在 YAML 檔案中明確加入 <code>permissions</code> 區塊，賦予工作流寫入安全事件的權力：</p>



<p>YAML</p>



<pre class="wp-block-code"><code>permissions:
  actions: read
  contents: read
  security-events: write  # 這是上傳結果最關鍵的權限
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">3. 警告：<code>Code scanning is not enabled</code></h2>



<p><strong>問題點：</strong> 你的儲存庫（Repository）尚未正式啟用 Code Scanning 功能。</p>



<p><strong>操作步驟：</strong></p>



<ol start="1" class="wp-block-list">
<li>進入 GitHub 儲存庫的 <strong>Settings（設定）</strong>。</li>



<li>點擊左側選單的 <strong>Code security and analysis</strong>。</li>



<li>找到 <strong>Code scanning</strong> 並點擊 <strong>Set up</strong> 或確保其為開啟狀態。</li>
</ol>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>[!IMPORTANT]</p>



<p>如果這是<strong>私有儲存庫（Private Repo）</strong>，你必須擁有 <strong>GitHub Advanced Security</strong> 授權才能使用 CodeQL 的自動掃描功能。</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">快速檢查清單</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>問題類型</strong></td><td><strong>應對動作</strong></td></tr></thead><tbody><tr><td><strong>來自 Fork 的 PR</strong></td><td>到 <strong>Settings &gt; Actions &gt; General</strong> 確認是否允許 Fork 的 PR 執行工作流並存取秘密（Secrets）。</td></tr><tr><td><strong>多語言設定</strong></td><td>若有其他語言，請寫成：<code>languages: [ 'javascript', 'php' ]</code>。</td></tr><tr><td><strong>GitHub Enterprise</strong></td><td>如果你在企業版環境，請確認 Runner 是否可以連線到 GitHub 的 API 端點。</td></tr></tbody></table></figure>



<p>請問你這個專案是公開的還是組織內的私有專案呢？這會影響到權限設定的細節。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>討論串: Why doesn&#8217;t CodeQL support auditing PHP<br><a href="https://github.com/github/codeql/issues/12376">https://github.com/github/codeql/issues/12376</a></p>



<p>這是一個很常見的誤解。簡單來說：<strong>CodeQL 絕對支援 PHP</strong>，但在 GitHub Action 的某些特定版本或環境下，可能會因為「配置方式」導致它找不到 PHP。</p>



<p>關於你提到的 issue (#12376) 以及相關的討論，這裡有幾個核心重點幫助你理解：</p>



<h3 class="wp-block-heading">1. CodeQL 確實支援 PHP</h3>



<p>PHP 是 CodeQL 官方支援的語言之一（包括分析 SQL 注入、跨站腳本 XSS 等漏洞）。你可以從官方文件中看到 PHP 始終在支援清單中。</p>



<h3 class="wp-block-heading">2. 為什麼會出現 &#8220;Did not recognize php&#8221; 的錯誤？</h3>



<p>在該 issue 和類似案例中，通常是因為以下兩個原因：</p>



<ul class="wp-block-list">
<li><strong>版本相容性 (Action Version)：</strong> 如果你使用的 <code>github/codeql-action</code> 版本太舊，或者內含的 CodeQL CLI 版本不匹配，可能會導致語言包加載失敗。</li>



<li><strong>預設套件包 (Standard Bundles)：</strong>CodeQL 在執行時會下載一個包含所有語言規則的「套件包 (Bundle)」。如果下載過程中出錯，或是你指定的分析器路徑不對，就會報出找不到語言的錯誤。</li>



<li><strong>大小寫問題：</strong>CodeQL 對設定檔非常敏感，必須確保是全小寫的 <code>php</code>。</li>
</ul>



<h3 class="wp-block-heading">3. 如何解決這個問題？</h3>



<p>根據目前的最佳實踐，請檢查並修正你的 <code>.github/workflows/codeql.yml</code>：</p>



<h4 class="wp-block-heading">A. 更新 Action 版本（最重要）</h4>



<p>確保你使用的是 <code>v3</code>（目前的主流版本）：</p>



<p>YAML</p>



<pre class="wp-block-code"><code>- name: Initialize CodeQL
  uses: github/codeql-action/init@v3
  with:
    languages: php
</code></pre>



<h4 class="wp-block-heading">B. 手動指定 CodeQL 套件來源（如果問題持續）</h4>



<p>有時候 GitHub 的託管執行器（Runner）快取了壞掉的 CodeQL 版本。你可以嘗試強制它使用最新的版本：</p>



<p>YAML</p>



<pre class="wp-block-code"><code>- name: Initialize CodeQL
  uses: github/codeql-action/init@v3
  with:
    languages: php
    tools: latest # 強制下載最新版工具
</code></pre>



<h3 class="wp-block-heading">4. 針對你提到的權限錯誤（補充）</h3>



<p>你之前提到的第二個錯誤 <code>Warning: This run of the CodeQL Action does not have permission...</code> 其實才是主因。</p>



<p>當 CodeQL 因為<strong>權限不足</strong>無法從 GitHub API 獲取配置或下載必要的語言組件時，它可能會退回到一個「空狀態」，然後報錯說它「不認識 php」。</p>



<p><strong>請務必檢查：</strong></p>



<ul class="wp-block-list">
<li>你的 Repository 設置中：<strong>Settings &gt; Code security and analysis &gt; Code scanning</strong> 是否已啟動？</li>



<li>如果這是 <strong>Fork</strong> 過來的專案，預設是不能執行 CodeQL 並回傳結果的（為了安全）。你必須在 PR 頁面手動核准執行，或者在該 Fork 倉庫的設定中開啟 Actions 權限。</li>
</ul>



<h3 class="wp-block-heading">總結</h3>



<p><strong>CodeQL 支援 PHP。</strong> 你看到的錯誤通常不是因為「不支援」，而是因為 <strong>權限被阻擋</strong> 或 <strong>Action 版本過舊</strong> 導致 CodeQL 無法正確加載 PHP 的分析模組。請嘗試將 Action 更新至 <code>v3</code> 並在 YAML 中加入 <code>permissions: security-events: write</code>。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<pre class="wp-block-code"><code>PHP is not supported by CodeQL. What should we do with <code>codeql-analysis.yml</code>?</code></pre>



<p>雖然 CodeQL 目前不支援 PHP，但這並不代表 codeql-analysis.yml 就完全沒有用處。如果你的專案除了 PHP 之外，還包含 JavaScript、TypeScript、HTML 或 CSS 等前端程式碼，CodeQL 依然可以掃描這些語言中的安全漏洞。建議先確認專案的語言組成，如果確實只有純 PHP 程式碼，直接移除這個檔案是合理的選擇，這樣可以避免 CI 執行無意義的掃描，節省系統資源。</p>



<p>如果專案包含前端程式碼，你可以修改設定檔，將語言限制在 CodeQL 支援的範圍內。如果你決定移除它，第一選項提到的 PHPStan 確實已經處理了大部分的靜態分析任務。不過要注意的是，PHPStan 主要專注於型別檢查和邏輯錯誤，而 CodeQL 類型的工具通常更偏向安全性漏洞掃描（如 SQL 注入）。如果你對安全性有高度要求，可以考慮第二選項，導入像 Snyk 或 Psalm 的安全分析外掛，來補足安全性掃描的空缺。</p>



<p>若選擇移除，請直接刪除 .github/workflows/codeql-analysis.yml。若專案未來計畫引入其他語言，則可以考慮暫時停用而非刪除。總結來說，若專案結構單一且 PHPStan 已能滿足開發需求，採取第一選項移除檔案是最簡潔的做法。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>這兩款工具在 PHP 社群都非常知名，但定位和收費模式完全不同。簡單來說，Psalm 適合預算有限、追求深度程式碼檢查的專案；Snyk 則適合企業級開發，需要更完整的安全性掃描生態。</p>



<h3 class="wp-block-heading">Psalm 安全分析</h3>



<p>Psalm 本身是<strong>完全免費且開源</strong>的靜態分析工具。它最強大的地方在於 Taint Analysis（汙點分析），可以追蹤不受信任的輸入（如 $_GET）是否流向了危險的函數（如 query()），從而檢測 SQL 注入或 XSS。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>安裝步驟</strong></p>



<p>如果你已經有專案，可以使用 Composer 進行安裝：</p>



<ol start="1" class="wp-block-list">
<li><strong>安裝主程式：</strong><code>composer require --dev vimeo/psalm</code></li>



<li><strong>初始化設定：</strong><code>./vendor/bin/psalm --init</code>這會生成一個 psalm.xml 設定檔。</li>



<li><strong>啟用安全分析：</strong>在 psalm.xml 中確保啟用了 taintAnalysis，或是直接在執行時加入參數：<code>./vendor/bin/psalm --taint-analysis</code></li>



<li><strong>安裝框架外掛（選配）：</strong>如果你使用 Laravel 或 Symfony，建議安裝專屬外掛，分析會更準確：<code>composer require --dev psalm/plugin-laravel</code><code>./vendor/bin/psalm-plugin enable psalm/plugin-laravel</code></li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">Snyk 安全掃描</h3>



<p>Snyk 是一個商業產品。對於<strong>開源專案或個人小規模使用</strong>，它提供免費額度（Free Plan），但對於企業或需要大量掃描、進階合規報告的團隊，則需要付費。2026 年的 Team 版本起跳價大約在每位開發者每月 50 美元左右。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p><strong>安裝步驟</strong></p>



<p>Snyk 通常是透過 CLI 或直接整合到 GitHub 工作流中。</p>



<ol start="1" class="wp-block-list">
<li><strong>安裝 Snyk CLI：</strong>在本地或 CI 環境執行：<code>npm install -g snyk</code></li>



<li><strong>驗證身份：</strong><code>snyk auth</code>這會跳轉到瀏覽器讓你登入帳號。</li>



<li><strong>進行掃描：</strong>在專案根目錄執行以下指令來檢查依賴套件與程式碼漏洞：<code>snyk test</code></li>



<li><strong>GitHub 工作流整合：</strong>你可以在 .github/workflows 中建立 snyk.yml，使用官方提供的 Action：YAML<code>steps: - uses: actions/checkout@v4 - uses: snyk/actions/php@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}</code></li>
</ol>



<h3 class="wp-block-heading">該選哪一個</h3>



<ul class="wp-block-list">
<li>如果你想要<strong>不花一毛錢</strong>就達到極致的程式碼安全檢查，選 <strong>Psalm</strong>。它對 PHP 的理解非常深，但設定上會比較囉嗦。</li>



<li>如果你希望有<strong>漂亮的儀表板</strong>、自動掃描開源套件（SCA）的漏洞，且不介意免費額度的限制，選 <strong>Snyk</strong>。它的強項在於全面性的漏洞資料庫。</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">解決 Composer 找不到的問題</h3>



<p>如果你是在 Windows 環境下，最簡單的解決方法是去 Composer 官網下載並執行 Composer-Setup.exe 安裝檔。</p>



<p>傳送門: <a href="https://getcomposer.org/download/">https://getcomposer.org/download/</a></p>



<p>安裝過程中它會自動尋找你的 php.exe 路徑，並幫你把執行命令設定好。安裝完成後，務必重新啟動你的終端機（如 CMD 或 PowerShell），這樣設定才會生效。</p>



<p>如果你確定已經安裝過，請檢查 PHP 的安裝路徑是否已經加入到系統環境變數中。你可以在終端機輸入 php -v 來確認。如果連 php 都不被辨認，那代表你必須先搞定 PHP 的環境。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">關於在本機檢查</h3>



<p>是的，當你執行 <code>composer require --dev vimeo/psalm</code> 時，這是在你的<strong>開發本機</strong>進行操作。</p>



<p>這行指令的意思是將 Psalm 下載到你專案資料夾裡的 vendor 目錄中。<code>--dev</code> 參數表示這個工具只在開發環境使用，實際把程式碼上傳到正式伺服器時，不會包含這些測試工具。</p>



<p>在本機檢查的好處是，你可以在程式碼還沒上傳到 GitHub 或伺服器之前，就先發現潛在的安全性問題。這通常被稱為「左移測試」（Shift-Left Testing），也就是在開發初期就介入檢查。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h3 class="wp-block-heading">執行本機檢查的流程</h3>



<p>當你解決了 Composer 的問題後，在本機操作的完整流程如下：</p>



<p><strong>安裝：</strong> 執行 </p>



<pre class="wp-block-code"><code>composer require --dev vimeo/psalm</code></pre>



<p><strong>初始化：</strong> 執行 </p>



<pre class="wp-block-code"><code>./vendor/bin/psalm --init</code></pre>



<p>這會在你的專案裡產生一個設定檔，讓 Psalm 知道要檢查哪些資料夾。</p>



<p><strong>執行掃描：</strong> 執行 </p>



<pre class="wp-block-code"><code>./vendor/bin/psalm</code></pre>



<p>它會開始掃描你的程式碼並在終端機顯示錯誤報告。</p>



<p>如果你覺得在本機手動執行很麻煩，通常我們會把這套流程整合進 VS Code 等編輯器的外掛中，讓你邊寫程式時，編輯器就直接畫紅線提醒你哪裡可能有安全性漏洞。</p>



<h3 class="wp-block-heading">為什麼這比在 GitHub 上檢查快</h3>



<p>在本機執行檢查的反應速度最快。如果你只靠 GitHub Actions（也就是你原本提到的 <code>codeql-analysis.yml</code> 所在的環境）來檢查，你必須先 commit 並 push 程式碼，等待雲端主機啟動，然後才能看到結果。在本機跑 Psalm 通常只需要幾秒鐘，能節省大量的等待時間。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>● CodeQL 完全不支援 PHP，這也是我們移除 codeql-analysis.yml 的原因。</p>



<p>支援的語言只有：C/C++、C#、Go、Java/Kotlin、JavaScript/TypeScript、Python、Ruby、Swift。</p>



<p>針對 PHP 的替代方案：</p>



<pre class="wp-block-preformatted">┌────────────────────────────────┬───────────────────────────────────┐<br>│ 工具 │ 用途 │<br>├────────────────────────────────┼───────────────────────────────────┤<br>│ Psalm (--taint-analysis) │ 資安漏洞（SQL injection、XSS 等） │<br>├────────────────────────────────┼───────────────────────────────────┤<br>│ PHPStan │ 型別錯誤、靜態分析 │<br>├────────────────────────────────┼───────────────────────────────────┤<br>│ PHP CodeSniffer │ 程式碼風格（PSR-12） │<br>├────────────────────────────────┼───────────────────────────────────┤<br>│ composer audit │ 相依套件已知漏洞 │<br>└────────────────────────────────┴───────────────────────────────────┘</pre>



<p>這些都能在本機直接執行，速度比 GitHub Actions 快很多。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/04/codeql-%e6%94%af%e6%8f%b4-php-%e5%97%8e/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>當 AI 把你的文章複製貼上三次：一個讓人又笑又哭的 Gemini CLI 偵錯紀錄</title>
		<link>https://stackoverflow.max-everyday.com/2026/04/ai-paste-multi-times-gemini-cli/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/04/ai-paste-multi-times-gemini-cli/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 02 Apr 2026 05:48:39 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8232</guid>

					<description><![CDATA[為什麼 Skill Mode 下 keyword...]]></description>
										<content:encoded><![CDATA[
<p>為什麼 Skill Mode 下 keyword.md 會重複寫入？三個根本原因、三個修法，還有一些讓你少掉幾根頭髮的教訓。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 本文目錄</h2>



<ol class="wp-block-list">
<li><a href="#intro">事情是這樣開始的</a></li>



<li><a href="#what-happened">發生了什麼事：文章被複製三次</a></li>



<li><a href="#root-causes">根本原因大揭秘（共三個，一個比一個妙）</a></li>



<li><a href="#fix">怎麼修：三個外科手術等級的改法</a></li>



<li><a href="#lesson">血淚教訓：寫 AI Prompt 規格時你一定要知道的事</a></li>



<li><a href="#tldr">懶人包 TL;DR</a></li>
</ol>



<h2 class="wp-block-heading" id="intro">事情是這樣開始的</h2>



<p>想像一下你打開了一個剛生成的&nbsp;<code>keyword.md</code>，然後你看到這個：</p>



<pre class="wp-block-code"><code># 遠端工作的真實面貌

遠端工作聽起來很美好，穿著睡衣開會、貓咪在旁邊陪...

（此處省略 1200 字精彩文章）

---

# 遠端工作的真實面貌

遠端工作聽起來很美好，穿著睡衣開會、貓咪在旁邊陪...

（此處又省略 1200 字一模一樣的文章）

---

# 遠端工作的真實面貌

（你沒看錯，還有第三份）</code></pre>



<p>恭喜你，你的 AI 助理今天特別勤勞，把同一篇文章幫你寫了三遍。 這不是功能，這是 Bug。而且是那種「看起來好像在工作」的 Bug， 最讓人崩潰。</p>



<p>這是我們在開發&nbsp;<strong>blog-pro-max</strong>（一套把 Skill 注入到 18 種 AI 平台的部落格寫作工具）時， 在 Gemini CLI 的 Skill Mode 上踩到的真實坑。這篇文章就是完整的偵錯過程紀錄， 希望你看完之後，可以省下我花掉的那幾個小時。</p>



<h2 class="wp-block-heading" id="what-happened">發生了什麼事：文章被複製三次</h2>



<h3 class="wp-block-heading">系統架構快速說明</h3>



<p>blog-pro-max 的運作方式是這樣的：</p>



<ol class="wp-block-list">
<li>用 <code>blogpro init --ai gemini</code> 把一個 <strong>SKILL.md</strong> 注入到 Gemini CLI</li>



<li>Gemini CLI 讀取 SKILL.md，知道自己有「寫部落格文章」的能力</li>



<li>使用者輸入指令，Gemini CLI 依照 SKILL.md 的規格執行：生成文章、做分析、存檔</li>
</ol>



<p>理論上，最終應該輸出三個乾淨的檔案：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><th class="has-text-align-left" data-align="left">檔案</th><th class="has-text-align-left" data-align="left">內容</th></tr><tr><td><code>output/keyword.md</code></td><td>文章本文（乾淨，只有文章）</td></tr><tr><td><code>output/keyword_analysis.md</code></td><td>所有分析報告（標題建議、審稿、趨勢⋯⋯）</td></tr><tr><td><code>output/keyword.html</code></td><td>兩者合併的完整 HTML，一頁看完</td></tr></tbody></table></figure>



<p>實際上，<code>keyword.md</code>&nbsp;裡面長這樣：</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 實際輸出（節錄）</p>



<p>文章本文 × 1 → 邏輯審稿報告（包含再次輸出的文章本文）× 1 → 結構審稿報告（又包含文章本文）× 1 → &#8230;</p>



<p><strong>最後 keyword.md 的大小：應有 3KB，實際有 27KB。</strong></p>



<h2 class="wp-block-heading" id="root-causes">根本原因大揭秘（共三個，一個比一個妙）</h2>



<p>找 Bug 的第一步，當然是把程式碼放在眼前盯著看，然後說「這不可能啊」。 讓我們來逐一拆解。</p>



<p><strong>Bug #1</strong></p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f916.png" alt="🤖" class="wp-smiley" style="height: 1em; max-height: 1em;" /> LLM 每產一段分析就 write_file 一次</p>



<p>SKILL.md 的 LLM-only 模式指示 Gemini CLI：「執行全科檢查、標題建議、封面提示詞、時事趨勢……」 沒有明確說什麼時候存檔。於是 Gemini CLI 很有 initiative 地選擇：&nbsp;<strong>每產出一段分析，就立刻呼叫 write_file 存一次。</strong></p>



<p>問題是：LLM 在呼叫 write_file 的時候，通常不是「附加模式（append）」， 而是<strong>重新生成整個檔案內容</strong>。它會把整個對話脈絡（包含剛才輸出的文章本文） 一起寫進去。然後下一段分析完成，又重新寫一次，文章本文又出現一次。</p>



<p>執行 12 段分析 = 文章本文出現 13 次（第一次正常存，後面 12 次每次都夾帶一份）。 恭喜你，獲得免費的 ×13 文章。</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 核心觀念</p>



<p>LLM 的 write_file 工具呼叫是<strong>覆寫（overwrite）</strong>，不是 append。 每次呼叫都會寫入你在 prompt context 裡看到的「目前完整內容」。 你以為在附加，AI 其實在複製貼上。</p>



<p><strong>Bug #2</strong></p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f92f.png" alt="🤯" class="wp-smiley" style="height: 1em; max-height: 1em;" /> LLM-only 區塊裡偷偷藏了腳本指令（邏輯矛盾）</p>



<p>這個 Bug 讓我看了三遍才看懂。在 SKILL.md 的「<strong>不具備執行腳本能力</strong>」區塊裡， 步驟 5 有這樣一行：</p>



<pre class="wp-block-code"><code>- 若 AI 具備執行腳本能力，執行：
    python output_md2html.py output/keyword.md output/keyword.html \
      --analysis output/keyword_analysis.md</code></pre>



<p>等等……這在「<em>不</em>具備腳本能力」的區塊裡，說的是「若<em>具備</em>腳本能力」？？</p>



<p>這是某次重構時的遺留程式碼（我知道，不要笑）。 Gemini CLI 解讀這個邏輯的方式是： 「我是 Gemini CLI，我可以執行腳本，所以我要走腳本路徑。 但我現在也在 LLM 模式裡，所以我也要走 LLM 路徑。」 於是兩條路都走了，<strong>存了兩次</strong>。</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 這是什麼感覺</p>



<p>就像告示牌上寫：「如果你會開車，請停車。如果你不會開車，請停車。如果你會開車，請繼續開。」 然後有個人在十字路口停了三次。</p>



<p><strong>Bug #3</strong></p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1faa4.png" alt="🪤" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 腳本執行失敗時靜默回退到 LLM 模式</p>



<p><code>content_research.py</code>&nbsp;需要&nbsp;<code>GITHUB_TOKEN</code>&nbsp;或&nbsp;<code>OPENAI_API_KEY</code>&nbsp;才能呼叫 LLM。如果這些 key 沒設定，腳本會直接 crash。</p>



<p>但 SKILL.md 沒有說腳本失敗時要怎麼辦。於是 Gemini CLI 很有 can-do 精神地說： 「腳本不行沒關係，我自己用 LLM 生成！」</p>



<p>然後就悲劇了：腳本已經把文章部分寫入了&nbsp;<code>keyword.md</code>（或者創建了空檔案）， Gemini CLI 的 LLM 又重新生成一遍，疊加進去。 最後你打開&nbsp;<code>keyword.md</code>，看到的是腳本的殘骸 + LLM 的全文，拼在一起。</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 沉默的失敗是最可怕的</p>



<p>明確的錯誤訊息是你的朋友。靜默回退是你最危險的敵人。 永遠要明確指定「失敗時應該做什麼」，尤其是當執行結果會寫入檔案的時候。</p>



<h2 class="wp-block-heading" id="fix">怎麼修：三個外科手術等級的改法</h2>



<p>找到病因之後，修法其實相當直接。以下是每個 Bug 對應的修法。</p>



<p><strong>Fix #1</strong></p>



<p>明確指定「全部完成後一次性寫入」</p>



<p>在 SKILL.md 的 LLM-only 存檔規則前，加入明確的防護說明：</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 舊的（含糊）</p>



<p>自動執行所有分析：全科檢查、標題建議……</p>



<p>儲存規則：<br>&#8211; output/keyword.md：僅存文章本文<br>&#8211; output/keyword_analysis.md：所有分析結果<br>&#8211; output/keyword.html：合併</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 新的（明確）</p>



<p>執行順序（所有分析在記憶體中累積）：<br>全科檢查 → 標題 → 封面 → 時事趨勢 → &#8230;</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 存檔規則（必須嚴格遵守）：<br>&#8211; keyword.md：只呼叫 write_file 一次，之後絕對不再修改<br>&#8211; keyword_analysis.md：全部完成後才 write_file 一次<br>&#8211;&nbsp;<strong>禁止對同一個檔案呼叫多次 write_file</strong></p>



<p>關鍵字是「<strong>記憶體中累積</strong>」和「<strong>一次性寫入</strong>」。 給 LLM 的指令越明確，它越不會自由發揮。</p>



<p><strong>Fix #2</strong></p>



<p>把錯誤嵌套的腳本指令移出 LLM-only 區塊</p>



<p>把那行混入 LLM-only 區塊的&nbsp;<code>output_md2html.py</code>&nbsp;指令直接刪除。 LLM-only 模式就讓 LLM 自己生成 HTML，不要夾帶腳本指令。</p>



<p>改完之後，兩個區塊變得清清楚楚、互不干擾：</p>



<pre class="wp-block-code"><code>/* 具備腳本能力 */
→ 執行 content_research.py（腳本自己搞定一切）
→ 回報結果

/* 不具備腳本能力（純 LLM） */
→ 生成文章 + 執行所有分析（全在 memory）
→ 一次性寫三個檔案
→ HTML 直接用 LLM 合併兩個 .md 的內容輸出</code></pre>



<p><strong>Fix #3</strong></p>



<p>在腳本模式加「失敗就停，別偷偷改模式」</p>



<p>在「具備腳本能力」的步驟 5 後面，加上明確的失敗處理指令：</p>



<pre class="wp-block-code"><code>&gt; &#x26a0; 若腳本執行失敗（例如缺少 API 金鑰或環境問題），
&gt; 請直接顯示錯誤訊息並停止。
&gt; 不要改用 LLM 模式重新生成文章，
&gt; 以避免與腳本已寫入的部分內容重疊。</code></pre>



<p>就這一段話。十秒鐘加進去的文字，省了好幾次「為什麼文章又重複了」的困惑。</p>



<h2 class="wp-block-heading" id="lesson">血淚教訓：寫 AI Prompt 規格時你一定要知道的事</h2>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Lesson 1：LLM 的 write_file ≠ append</h3>



<p>這是最根本的認知差距。你可能以為 LLM 在「附加分析到 .md 檔」， 但它實際上是「重新生成完整的 .md 內容後覆寫」。&nbsp;<strong>在任何涉及分次寫入的場景，都必須明確指定「collect → write once」的順序。</strong></p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Lesson 2：Prompt 的分支邏輯要互斥且完整</h3>



<p>if A → do X，if not A → do Y。這兩個分支要確保：</p>



<ul class="wp-block-list">
<li>互斥（進了 A 就不能再進 not-A 的分支）</li>



<li>每個分支裡只有屬於自己的指令（不要偷渡對方的內容）</li>



<li>都有失敗處理（明確說失敗時要做什麼，不要讓 AI 自由心證）</li>
</ul>



<p>人寫 if/else 都會 bug，更何況是自然語言寫給 LLM 的條件分支。</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Lesson 3：靜默回退（Silent Fallback）是所有 Bug 的老爸</h3>



<p>在 AI agent 系統裡，「我做不到就換個方式做」聽起來很聰明， 但如果沒有明確說「換個方式前要先清除之前的影響」， 就會出現兩個執行路徑同時留下痕跡的混亂狀態。</p>



<p><strong>規則很簡單：可以 fallback，但必須先 cleanup；或者根本禁止 fallback，直接 fail-fast。</strong>&nbsp;後者在寫入檔案的場景通常更安全。</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Lesson 4：Prompt Spec 也要 Code Review</h3>



<p>SKILL.md 裡的那個「LLM-only 區塊藏著腳本指令」的 Bug， 是重構時從其他地方複製過來的殘留程式碼。</p>



<p>普通程式碼有 linter、有 type checker、有 unit test。 Prompt 規格文件什麼都沒有。 所以在你覺得「這個 prompt 寫好了」的時候，最好把它當成 code 一樣做一次&nbsp;<strong>邏輯審查</strong>：每個分支都走一遍，看看有沒有矛盾的指令。</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Lesson 5：「應該很直觀」是 Bug 最常見的藏身地</h3>



<p>「LLM 應該知道只要寫一次吧？」 「腳本失敗了應該會跳錯吧？」 「分支應該不會混在一起吧？」</p>



<p>以上三句話，分別對應了本文的三個 Bug。 凡是「應該」的地方，就是你需要寫明確指令的地方。 在人機協作的系統裡，不要假設任何「顯而易見」的行為。</p>



<h2 class="wp-block-heading" id="tldr">懶人包 TL;DR</h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><th class="has-text-align-left" data-align="left">Bug</th><th class="has-text-align-left" data-align="left">根本原因</th><th class="has-text-align-left" data-align="left">修法</th></tr><tr><td>文章本文重複 N 次</td><td>LLM 每段分析後各自 write_file，覆寫時夾帶完整 context</td><td>明確指定「全部在記憶體累積，最後一次性 write_file」</td></tr><tr><td>同一份檔案被存兩次</td><td>LLM-only 區塊裡偷藏了腳本指令，兩條路都走</td><td>移除嵌套的錯誤指令，確保分支互斥</td></tr><tr><td>腳本 + LLM 輸出疊加</td><td>腳本失敗時靜默回退 LLM 模式，兩者輸出重疊</td><td>加入「腳本失敗停止，禁止靜默回退」明確指令</td></tr></tbody></table></figure>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 修完之後的感覺</p>



<p>打開&nbsp;<code>keyword.md</code>，看到乾淨的文章本文，只有文章本文，沒有重複， 分析全部在&nbsp;<code>keyword_analysis.md</code>，HTML 合併得漂漂亮亮。 這種「終於正常了」的快感，大概是所有開發者共同的語言。</p>



<p>如果你也在開發類似的 AI agent 系統，希望這篇文章能幫你少踩幾個坑。 如果你踩到的坑比我還奇特，歡迎留言分享，讓大家一起開心（或一起哭）。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/04/ai-paste-multi-times-gemini-cli/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Xcopy 存取遭到拒絕解決方案</title>
		<link>https://stackoverflow.max-everyday.com/2026/04/xcopy-git-fail/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/04/xcopy-git-fail/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 01 Apr 2026 10:34:23 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8226</guid>

					<description><![CDATA[這是在執行 xcopy 指令時，因為嘗試複製 ....]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="420" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/WindowsTerminal_2026-04-01-18-32-4m-1024x420.jpg?v=1775039604" alt="" class="wp-image-8227" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/WindowsTerminal_2026-04-01-18-32-4m-1024x420.jpg?v=1775039604 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/WindowsTerminal_2026-04-01-18-32-4m-600x246.jpg?v=1775039604 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/WindowsTerminal_2026-04-01-18-32-4m-768x315.jpg?v=1775039604 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/WindowsTerminal_2026-04-01-18-32-4m.jpg?v=1775039604 1169w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>這是在執行 xcopy 指令時，因為嘗試複製 .git 資料夾內的系統檔案或正在被使用的檔案，導致權限不足或檔案被鎖定而中斷。當你使用 /s 參數時，xcopy 會連同隱藏的設定檔一起搬移，但 Git 的內部資料庫通常不建議這樣直接複製。</p>



<p>解決這個問題最快的方法是排除 .git 資料夾，因為你通常只需要程式碼檔案，而不需要把舊的 Git 紀錄也強制蓋過去。你可以改用 robocopy 指令，它比 xcopy 更強大，且可以輕鬆排除特定目錄。</p>



<p>請嘗試執行以下指令：</p>



<pre class="wp-block-code"><code>robocopy "c:\max\play\blog-pro-max" "c:\max\git\blog-pro-max" /s /e /xd .git
</code></pre>



<p>這個指令的原理是將來源路徑的所有子目錄與檔案複製到目標路徑，但透過 /xd 參數把 .git 資料夾排除在外。這樣就能避免觸碰那些會導致存取被拒的隱藏系統檔案，同時確保你的程式碼與文件能順利完成同步。</p>



<p>如果你堅持要連同 Git 紀錄一起複製，請先確認沒有任何程式（例如 VS Code 或 Git GUI）正在開啟該專案，並嘗試以系統管理員身分執行命令提示字元。不過實務上，使用 robocopy 排除不必要的元資料是比較穩健的做法。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/04/xcopy-git-fail/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>git push 每次都要輸入密碼鎖(passphrase)的解法</title>
		<link>https://stackoverflow.max-everyday.com/2026/04/git-push-passphrase/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/04/git-push-passphrase/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Wed, 01 Apr 2026 09:17:05 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8219</guid>

					<description><![CDATA[之前產生的 SSH 金鑰有設定密碼鎖。雖然這比沒...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="559" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/git-push-passphrase_clean-1024x559.jpg?v=1775035163" alt="" class="wp-image-8222" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/git-push-passphrase_clean-1024x559.jpg?v=1775035163 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/git-push-passphrase_clean-600x327.jpg?v=1775035163 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/git-push-passphrase_clean-768x419.jpg?v=1775035163 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/git-push-passphrase_clean.jpg?v=1775035163 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>之前產生的 SSH 金鑰有設定密碼鎖。雖然這比沒設密碼安全，但每次都要輸入確實很麻煩。如果你不想重新產生金鑰，有兩個方法可以解決。</p>



<h2 class="wp-block-heading">使用 ssh-agent 自動管理</h2>



<p>這是最推薦的做法。你可以讓系統在背景跑一個代理程式，幫你記住解鎖後的金鑰。只要在 WSL 終端機執行這兩行指令：</p>



<pre class="wp-block-code"><code>eval $(ssh-agent -s)
ssh-add ~/.ssh/id_rsa_personal</code></pre>



<p>執行第二行時，輸入最後一次密碼。之後只要這個視窗沒關掉，再次 push 就不會再問你密碼。</p>



<p>如果你希望每次開啟 WSL 都自動生效，可以把這兩行指令加到 ~/.bashrc 檔案的最末端。這樣每天第一次開機輸入一次，之後整天都不用再打。</p>



<h2 class="wp-block-heading">移除金鑰本身的密碼</h2>



<p>如果你覺得在私有電腦上不需要這層保護，也可以直接把金鑰的密碼拿掉。執行這行指令：</p>



<pre class="wp-block-code"><code>ssh-keygen -p -f ~/.ssh/id_rsa_personal</code></pre>



<p>系統會先要求你輸入舊密碼。接著會問你新的密碼，這時直接按兩次 Enter 留白。這樣這組金鑰就會變成無密碼狀態，以後執行 git push 就會直接通過。</p>



<h2 class="wp-block-heading">確認設定是否正確</h2>



<p>處理完密碼後，你可以測試一下連線是否正常。輸入：</p>



<pre class="wp-block-code"><code>ssh -T git@github.com -i ~/.ssh/id_rsa_personal</code></pre>



<p>如果看到 Hi 帳號名！You&#8217;ve successfully authenticated，就代表搞定了。以後不管 Windows 那邊登入哪個帳號，WSL 這邊都會走你這組專屬的 SSH 金鑰。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>如果你使用的是 zsh，要把設定寫在 <strong>~/.zshrc</strong> 這個檔案裡。</p>



<h2 class="wp-block-heading">修改設定檔</h2>



<p>你可以使用 nano 或 vim 來編輯：</p>



<pre class="wp-block-code"><code>nano ~/.zshrc</code></pre>



<p>把下面這段程式碼貼到檔案的最末端：</p>



<p>Bash</p>



<pre class="wp-block-code"><code>if &#91; -z "$SSH_AUTH_SOCK" ]; then
   eval $(ssh-agent -s)
   ssh-add ~/.ssh/id_rsa_personal
fi
</code></pre>



<p>存檔離開後，執行 <strong>source ~/.zshrc</strong> 讓設定立即生效。這樣你下次開啟新的 zsh 視窗時，系統就會自動啟動代理並載入你的金鑰。</p>



<h2 class="wp-block-heading">使用 oh-my-zsh 插件</h2>



<p>如果你有安裝 oh-my-zsh，其實有一個更優雅的插件做法。在 ~/.zshrc 找到 <strong>plugins=(&#8230;)</strong> 這行，把 <strong>ssh-agent</strong> 加進去：</p>



<pre class="wp-block-code"><code>plugins=(git ssh-agent)</code></pre>



<p>接著在下面補上一行設定，指定要自動載入的金鑰名稱：</p>



<pre class="wp-block-code"><code>zstyle :omni:plugins:ssh-agent identities id_rsa_personal</code></pre>



<p>這樣它會在你開啟終端機時自動處理一切，不用自己寫 shell script。</p>



<h2 class="wp-block-heading">解決重複啟動問題</h2>



<p>要注意的是，如果直接在設定檔寫 eval，每次開新視窗都會產生一個新的 ssh-agent 處理程序。如果你發現系統變慢，或是用 ps 指令看到一堆 ssh-agent，建議使用上面提到的 if 判斷式，或是安裝 <strong>keychain</strong> 這個套件來管理。</p>



<p>keychain 會幫你找現有的代理程序，確保整個系統只跑一個 ssh-agent。這對於習慣開多個分頁的人來說，是比較乾淨的做法。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">關於 Keychain</h2>



<p>keychain 是一個用來管理 ssh-agent 的工具。它的好處是能讓所有開啟的終端機視窗共用同一個 ssh-agent。如果不使用它，你每開一個新的 zsh 分頁，系統可能就會跑一個新的 ssh-agent 程式，久了會變得很亂。</p>



<p>keychain 會在背景記住你解鎖過的金鑰。只要你在第一次登入時輸入過一次密碼，之後不管是重開分頁還是執行 git push，它都不會再煩你。</p>



<h2 class="wp-block-heading">安裝方式</h2>



<p>在 Ubuntu 或 WSL (Ubuntu) 環境下，安裝非常簡單。直接輸入這行指令：</p>



<pre class="wp-block-code"><code>sudo apt update &amp;&amp; sudo apt install keychain</code></pre>



<p>安裝完後，它還不會自動運作，需要去修改你的 zsh 設定檔。</p>



<h2 class="wp-block-heading">設定 zsh 自動執行</h2>



<p>你需要把啟動指令加進 <strong>~/.zshrc</strong>。請編輯該檔案：</p>



<p>nano ~/.zshrc</p>



<p>在檔案最後面加上這兩行程式碼：</p>



<p>Bash</p>



<pre class="wp-block-code"><code>/usr/bin/keychain --nogui ~/.ssh/id_rsa_personal
source ~/.keychain/$HOST-sh
</code></pre>



<p>第一行是叫 keychain 去管理你的那把金鑰。第二行則是載入相關的環境變數，讓目前的 zsh 知道要去哪裡找已經解鎖的金鑰。</p>



<h2 class="wp-block-heading">實際使用效果</h2>



<p>設定完成後，執行 <strong>source ~/.zshrc</strong> 或是重開視窗。這時候系統會最後一次詢問你金鑰的密碼。</p>



<p>輸入完畢後，這把金鑰就會一直保持在解鎖狀態。就算你開了十個 zsh 視窗，也都只需要輸入那一次密碼。這對頻繁使用 git push 的開發者來說，是最省心也兼顧安全的方法。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>在 Windows CMD 環境下，Git 每次 push 都要求輸入密碼鎖，通常是因為 SSH Agent 沒有在背景執行，或是金鑰沒有被正確載入。以下提供兩個主要的解決方案。</p>



<h3 class="wp-block-heading">使用 Windows 內建的 OpenSSH 服務</h3>



<p>這是最推薦的做法，因為它可以讓 SSH Agent 在開機時自動啟動，並持久化儲存你的金鑰密碼。</p>



<p>以管理員身分開啟 PowerShell，執行以下指令將 OpenSSH Authentication Agent 服務設定為自動啟動</p>



<pre class="wp-block-code"><code>Set-Service -Name ssh-agent -StartupType AutomaticStart-Service ssh-agent</code></pre>



<p>回到一般的 CMD 視窗，將你的私鑰加入 Agent。假設你的金鑰路徑是預設值：</p>



<pre class="wp-block-code"><code>ssh-add %USERPROFILE%\.ssh\id_rsa</code></pre>



<p>執行後系統會要求你輸入最後一次密碼鎖。之後只要服務維持執行狀態，重開 CMD 也不需要再輸入密碼。</p>



<p>執行上述指令, 錯誤訊息:</p>



<pre class="wp-block-preformatted">Error connecting to agent: No such file or directory</pre>



<p>這個錯誤通常是因為 CMD 無法識別 %USERPROFILE% 後接續的路徑格式，或是 ssh-agent 服務雖然啟動了，但目前的視窗環境變數還沒有對接上。你可以依照以下步驟排查並修正。</p>



<h3 class="wp-block-heading">修正路徑格式與環境變數</h3>



<p>在 CMD 中，路徑的分隔符號與變數引用非常嚴格。請嘗試手動補上反斜線，並加上雙引號確保路徑正確：</p>



<pre class="wp-block-code"><code>ssh-add "%USERPROFILE%\.ssh\id_rsa"</code></pre>



<p>如果依然顯示無法連線到 Agent，請在同一個 CMD 視窗內執行以下指令，手動啟動 Agent 並設定環境變數：</p>



<pre class="wp-block-code"><code>for /f "tokens=1-2 delims==;" %a in ('ssh-agent -s') do set %a=%b</code></pre>



<p>執行完畢後，再次嘗試執行 ssh-add 指令。</p>



<h3 class="wp-block-heading">確認 SSH 服務執行狀態</h3>



<p>如果手動啟動有效，但每次開新視窗都要重來，代表系統服務沒有正確常駐。請再次確認服務狀態：</p>



<ol start="1" class="wp-block-list">
<li>按下 Win + R 鍵，輸入 services.msc 並執行。</li>



<li>找到 OpenSSH Authentication Agent。</li>



<li>確認狀態為 正在執行，且啟動類型為 自動。</li>
</ol>



<p>結果是服務被停用, 啟用就好了.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="590" height="710" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-02_11-51_n3.jpg?v=1775101951" alt="" class="wp-image-8230" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-02_11-51_n3.jpg?v=1775101951 590w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/04/2026-04-02_11-51_n3-499x600.jpg?v=1775101951 499w" sizes="auto, (max-width: 590px) 100vw, 590px" /></figure>



<h3 class="wp-block-heading">檢查 GIT_SSH 環境變數</h3>



<p>有時候安裝 Git 時會內建自家的 SSH 套件，這會干擾系統內建的 ssh-add。請檢查你的環境變數中是否有名為 GIT_SSH 的變數。如果有，請將它刪除，讓 Git 預設使用系統路徑 C:\Windows\System32\OpenSSH\ssh.exe。</p>



<p>另外，請確認你的 .ssh 資料夾內確實存在 id_rsa 這個檔案。你可以輸入 dir %USERPROFILE%.ssh 來查看檔案清單。如果你的金鑰檔名不是 id_rsa (例如是 id_ed25519)，請將指令中的檔名更換為正確的名稱。</p>



<h3 class="wp-block-heading">設定 Git 使用內建的 SSH 工具</h3>



<p>有時候 Git 會使用自己打包的 SSH 工具而不是 Windows 系統路徑下的工具，這會導致兩者金鑰紀錄不互通。你可以強制 Git 指向系統的 SSH 路徑：</p>



<pre class="wp-block-code"><code>git config --global core.sshCommand C:/Windows/System32/OpenSSH/ssh.exe</code></pre>



<h3 class="wp-block-heading">修改金鑰設定移除密碼鎖</h3>



<p>如果你覺得維護 Agent 服務太麻煩，且確認電腦環境安全，也可以選擇直接移除私鑰的密碼鎖。</p>



<p>執行以下指令：</p>



<pre class="wp-block-code"><code>ssh-keygen -p -f %USERPROFILE%.ssh\id_rsa</code></pre>



<p>系統會先要求輸入舊密碼，當詢問新密碼時直接按兩次 Enter 留白。這樣該金鑰就會變成無密碼狀態，之後任何 Git 操作都不會再跳出輸入提示。不過請注意，這會降低金鑰外洩時的安全性。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/04/git-push-passphrase/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>5 分鐘學會 SSH Config：輕鬆管理你的所有伺服器金鑰</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/manage-ssh-keys-with-config/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/manage-ssh-keys-with-config/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 31 Mar 2026 04:25:02 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8208</guid>

					<description><![CDATA[很多人為了方便管理，會把預設的 SSH 私鑰檔名...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/manage-ssh-keys-with-config-1024x572.jpg?v=1774931085" alt="" class="wp-image-8209" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/manage-ssh-keys-with-config-1024x572.jpg?v=1774931085 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/manage-ssh-keys-with-config-600x335.jpg?v=1774931085 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/manage-ssh-keys-with-config-768x429.jpg?v=1774931085 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/manage-ssh-keys-with-config.jpg?v=1774931085 1376w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>很多人為了方便管理，會把預設的 SSH 私鑰檔名從&nbsp;<code>id_rsa</code>&nbsp;改成更有辨識度的名稱，例如&nbsp;<code>id_rsa_personal</code>。沒想到改完名後，原本可以秒連的伺服器突然都跳出權限錯誤。這不是因為金鑰失效，而是因為 SSH 客戶端「迷路」了。</p>



<h2 class="wp-block-heading">為什麼改名後會連線失敗？</h2>



<p>SSH 客戶端在發起連線時，預設只會自動尋找幾個固定名稱的檔案，像是&nbsp;<code>id_rsa</code>、<code>id_ecdsa</code>&nbsp;或&nbsp;<code>id_ed25519</code>。一旦你將檔案改名，原本依賴自動偵測的機制就會找不到對應的私鑰，導致連線被拒絕。</p>



<p>要修復這個問題，你不需要把檔名改回去，而是要學會使用 SSH 的「地圖」——也就是&nbsp;<code>config</code>&nbsp;檔案。</p>



<h2 class="wp-block-heading">使用 Config 建立通用對應規則</h2>



<p>你可以透過編輯&nbsp;<code>~/.ssh/config</code>&nbsp;檔案，告訴 SSH 客戶端你的金鑰躲在哪裡。請在該檔案中加入以下設定：</p>



<pre class="wp-block-code"><code>Host *
  IdentityFile ~/.ssh/id_rsa_personal
</code></pre>



<p>這段設定的意思是：無論連線到哪一個網域（Host *），如果找不到特定的金鑰，就統一嘗試使用這把&nbsp;<code>id_rsa_personal</code>。這樣一來，原本受影響的站點就能恢復正常連線。</p>



<p>舉例：這就像你在公司設定了一個「預設聯絡人」，不管客戶從哪裡打來，只要沒人接，電話就會自動轉給這個人。</p>



<h2 class="wp-block-heading">多把金鑰的精確分流做法</h2>



<p>如果你手邊有多把金鑰分別對應不同的帳號（例如公司用與私人用），建議採取更精確的分流設定。例如，針對 GitHub 使用特定的金鑰，而其他站點則使用另一把：</p>



<pre class="wp-block-code"><code>Host github.com
HostName github.com
IdentityFile ~/.ssh/id_rsa_personal

Host *
IdentityFile ~/.ssh/id_rsa_other</code></pre>



<p>透過這種方式，SSH 就會根據你連線的目標，自動選取正確的「鑰匙」開門，再也不用手動指定。</p>



<h2 class="wp-block-heading">連線診斷與最後提醒</h2>



<p>設定好之後，你可以使用&nbsp;<code>ssh -v</code>&nbsp;加上目標網域（例如&nbsp;<code>ssh -v git@github.com</code>）來觀察連線過程。在輸出的冗長資訊中，你會看到 SSH 開始嘗試載入你指定的&nbsp;<code>IdentityFile</code>。</p>



<p>在 Linux 或 WSL 的環境下，系統對金鑰檔案的安全性要求很高。如果你改名後檔案的權限跑掉了，系統會覺得這把鑰匙太危險而拒絕使用。</p>



<p>請檢查你的金鑰權限是否維持在 600（這代表只有你自己可以讀寫）。你可以輸入指令檢查，如果權限不對，連線是絕對不會成功的。</p>



<p>如果你平常有在使用 ssh-agent（這是一個幫你在背景管理鑰匙、讓你不用每次都輸入密碼的小工具），光是改掉檔案名稱是不夠的。</p>



<p>因為管理員的記憶中存的還是舊的名字。改名後，請記得執行 ssh-add ~/.ssh/id_rsa_personal（這裡的檔名請換成你修改後的名字），手動將新檔名的金鑰重新載入到記憶體中，這樣連線時系統才抓得到正確的鑰匙。</p>



<h2 class="wp-block-heading">結語</h2>



<p>管理 SSH 金鑰不該是一件痛苦的事。透過&nbsp;<code>~/.ssh/config</code>&nbsp;檔案，我們不僅能自由為金鑰命名，還能優雅地處理多個帳號的分流連線。下次遇到連線問題時，先檢查一下你的「地圖」(config)設定是否正確吧！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/manage-ssh-keys-with-config/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>安裝 github gh</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/install-github-gh/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/install-github-gh/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 30 Mar 2026 04:53:07 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8201</guid>

					<description><![CDATA[Ubuntu 或 Debian 系列的 WSL ...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="559" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/install-github-gh-1024x559.jpg" alt="" class="wp-image-8203" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/install-github-gh-1024x559.jpg?v=1774846371 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/install-github-gh-600x327.jpg?v=1774846371 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/install-github-gh-768x419.jpg?v=1774846371 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/install-github-gh.jpg?v=1774846371 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p> Ubuntu 或 Debian 系列的 WSL 中，建議使用官方的套件庫進行安裝。</p>



<h2 class="wp-block-heading">安裝步驟</h2>



<p>你可以依序執行下列指令來完成安裝。這會下載 GitHub 的官方金鑰並將套件來源加入你的清單中。</p>



<pre class="wp-block-code"><code>type -p curl &gt;/dev/null || (sudo apt update &amp;&amp; sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb &#91;arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list &gt; /dev/null
sudo apt update
sudo apt install gh -y</code></pre>



<h2 class="wp-block-heading">驗證安裝</h2>



<p>安裝完成後，請輸入指令確認版本。</p>



<pre class="wp-block-code"><code>gh --version</code></pre>



<p>如果畫面正確顯示版本資訊，代表安裝成功。</p>



<h2 class="wp-block-heading">登入帳號</h2>



<p>在使用 gh 建立 release 之前，你必須先進行授權，否則會因為權限不足被拒絕。請執行以下指令並依照畫面提示操作。</p>



<pre class="wp-block-code"><code>gh auth login</code></pre>



<p>通常建議選擇透過瀏覽器 (web browser) 登入，它會跳出一個視窗要求你輸入 GitHub 帳號密碼，或是給你一組 code 貼上。</p>



<h2 class="wp-block-heading">排除路徑問題</h2>



<p>如果你確定已經安裝過，但 zsh 仍然找不到，可以嘗試執行 source ~/.zshrc 重新讀取設定檔。如果是透過 Linuxbrew 安裝的，請檢查 /home/linuxbrew/.linuxbrew/bin 是否在你的 PATH 變數中。你可以輸入 echo $PATH 來檢查目前的環境變數路徑。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>在 Windows 上安裝 GitHub CLI (gh) 有幾種常見且快速的方法。根據你是否有使用套件管理員，可以選擇最適合你的方式。</p>



<h2 class="wp-block-heading">使用 Winget 安裝</h2>



<p>這是 Windows 10 與 11 內建的推薦方式，不需要額外下載安裝檔，直接在 PowerShell 或命令提示字元 (CMD) 執行即可。</p>



<pre class="wp-block-code"><code>winget install --id GitHub.cli</code></pre>



<h2 class="wp-block-heading">使用 Scoop 安裝</h2>



<p>如果你有在使用 Scoop 這個套件管理工具，可以用以下指令安裝。Scoop 通常會處理好所有路徑設定，對開發者非常友善。</p>



<pre class="wp-block-code"><code>scoop install gh</code></pre>



<h2 class="wp-block-heading">使用 Chocolatey 安裝</h2>



<p>如果你習慣使用 Chocolatey，請執行。</p>



<pre class="wp-block-code"><code>choco install gh</code></pre>



<h2 class="wp-block-heading">使用安裝程式 (MSI)</h2>



<p>如果你偏好傳統的點擊安裝方式，可以依照以下步驟操作。</p>



<ol start="1" class="wp-block-list">
<li>前往 GitHub CLI 的官方 Release 頁面。</li>



<li>下載檔名結尾為 .msi 的檔案 (例如 gh_2.x.x_windows_amd64.msi)。</li>



<li>執行該檔案並一路點擊下一步直到完成。</li>
</ol>



<h2 class="wp-block-heading">驗證與登入</h2>



<p>安裝完成後，請務必重新啟動你的終端機 (PowerShell、CMD 或 Windows Terminal)，然後執行指令檢查。</p>



<pre class="wp-block-code"><code>gh --version</code></pre>



<p>確認能讀取到版本後，執行登入指令。</p>



<pre class="wp-block-code"><code>gh auth login</code></pre>



<p>依照畫面提示選擇 GitHub.com，並使用瀏覽器進行授權即可完成設定。如果你之後要在 WSL 裡面使用，記得 WSL 與 Windows 的環境是分開的，需要在 WSL 內再安裝一次。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/install-github-gh/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>使用github 的 gh 指令, 在 repo 自動新增 tag 與 release</title>
		<link>https://stackoverflow.max-everyday.com/2026/03/github-gh-repo-release/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/03/github-gh-repo-release/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Mon, 30 Mar 2026 04:39:40 +0000</pubDate>
				<category><![CDATA[電腦相關應用]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8198</guid>

					<description><![CDATA[使用 GitHub CLI (gh) 自動化處理...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="559" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/github-gh-repo-release-1024x559.jpg" alt="" class="wp-image-8199" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/github-gh-repo-release-1024x559.jpg?v=1774845562 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/github-gh-repo-release-600x327.jpg?v=1774845562 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/github-gh-repo-release-768x419.jpg?v=1774845562 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/03/github-gh-repo-release.jpg?v=1774845562 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>使用 GitHub CLI (gh) 自動化處理 tag 與 release 非常方便，這可以讓你在不離開終端機的情況下完成原本需要在網頁上點擊的操作。首先請確保你已經安裝 gh 並完成登入。</p>



<h2 class="wp-block-heading">新增 Tag</h2>



<p>雖然 gh 主要用於處理 GitHub 特有的功能，但新增 tag 通常建議直接使用 git 指令，再搭配 gh 進行後續操作。你可以先在本地建立標籤並推送到遠端。</p>



<pre class="wp-block-code"><code>git tag 1.0.0
git push origin 1.0.0</code></pre>



<p>如果你想完全自動化，可以將這些指令寫在腳本中。</p>



<h2 class="wp-block-heading">建立 Release</h2>



<p>一旦 tag 存在於 GitHub 遠端，你就可以使用 gh release create 指令。最基本的用法是指定標籤名稱。</p>



<pre class="wp-block-code"><code>gh release create 1.0.0</code></pre>



<p>如果你希望在建立時直接指定標題和說明內容，可以使用參數。</p>



<pre class="wp-block-code"><code>gh release create 1.0.0 --title "正式版更新" --notes "修正了已知錯誤並提升效能"</code></pre>



<h2 class="wp-block-heading">自動化與檔案上傳</h2>



<p>在自動化流程中，你可能需要上傳編譯好的二進位檔或壓縮包，這時候只要在指令最後加上檔案路徑即可。</p>



<pre class="wp-block-code"><code>gh release create 1.0.0 ./dist/app.zip --generate-notes</code></pre>



<p>加上 &#8211;generate-notes 參數會讓 GitHub 自動根據兩次 release 之間的 commit 紀錄產生說明內容。如果你在 CI/CD 環境中使用，記得確保環境變數中包含有效的 GITHUB_TOKEN。</p>



<h2 class="wp-block-heading">常用參數說明</h2>



<p>如果你想要建立的是預發佈版本，可以加上 &#8211;prerelease 參數。如果你想先建立草稿而不直接發佈，可以使用 &#8211;draft。</p>



<pre class="wp-block-code"><code>gh release create 1.0.1 --draft --title "開發中版本"</code></pre>



<p>這組指令能幫你快速整合進 shell script 或 GitHub Actions，實現全自動的版號管理。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/03/github-gh-repo-release/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
