|
| 1 | +# Case Study: Issue #1902 - Prevent One-Off Log Repositories |
| 2 | + |
| 3 | +## Summary |
| 4 | + |
| 5 | +Issue #1902 reported that two log uploads created dedicated public repositories: |
| 6 | + |
| 7 | +- `konard/log-tmp-solution-draft-log-pr-1781180521736.txt` |
| 8 | +- `konard/log-tmp-solution-draft-log-pr-1781180537724.txt` |
| 9 | + |
| 10 | +Both uploaded files were under Hive Mind's 25 MB file limit, so they should have |
| 11 | +used gist uploads first through `gh-upload-log` auto mode. If gist upload fails |
| 12 | +and `gh-upload-log` falls back to repository mode, or if a log is larger than the |
| 13 | +gist limit, repository storage should use the shared visibility repositories: |
| 14 | +`public-logs` for public targets and `private-logs` for private targets. |
| 15 | + |
| 16 | +The root cause was in `gh-upload-log` 0.8.0 auto fallback routing. In the |
| 17 | +captured run, automatic mode first tried to create a gist. GitHub returned a |
| 18 | +secondary content-creation rate limit for the gist API. `gh-upload-log` then |
| 19 | +fell back to repository mode. Because the file still fit within the gist limit, |
| 20 | +upstream shared-repository routing did not apply, so the fallback created a |
| 21 | +dedicated `log-tmp-*` repository even though shared repository mode was enabled. |
| 22 | + |
| 23 | +The upstream bug was fixed in `gh-upload-log` 0.8.1 by |
| 24 | +[link-foundation/gh-upload-log#32](https://github.com/link-foundation/gh-upload-log/pull/32). |
| 25 | +Hive Mind now installs `gh-upload-log@latest` in its Docker images and relies on |
| 26 | +the package defaults for auto mode and shared repository fallback. |
| 27 | + |
| 28 | +## Captured Evidence |
| 29 | + |
| 30 | +| File | Purpose | |
| 31 | +| ------------------------------------------------------- | --------------------------------------------------------- | |
| 32 | +| `raw-data/issue-1902.json` | Issue title, body, labels, timestamps, and URL | |
| 33 | +| `raw-data/issue-1902-comments.json` | Issue comments, empty at capture time | |
| 34 | +| `raw-data/pr-1909.json` | Prepared PR metadata | |
| 35 | +| `raw-data/pr-1909-issue-comments.json` | PR conversation comments, empty at capture time | |
| 36 | +| `raw-data/pr-1909-review-comments.json` | PR inline comments, empty at capture time | |
| 37 | +| `raw-data/pr-1909-reviews.json` | PR reviews, empty at capture time | |
| 38 | +| `raw-data/linked-repo-1781180521736.json` | First linked one-off repository metadata | |
| 39 | +| `raw-data/linked-repo-1781180521736-contents.json` | First linked repository file metadata | |
| 40 | +| `raw-data/linked-repo-1781180521736-commits.json` | First linked repository initial commit | |
| 41 | +| `raw-data/linked-repo-1781180537724.json` | Second linked one-off repository metadata | |
| 42 | +| `raw-data/linked-repo-1781180537724-contents.json` | Second linked repository file metadata | |
| 43 | +| `raw-data/linked-repo-1781180537724-commits.json` | Second linked repository initial commit | |
| 44 | +| `raw-data/gh-upload-log-README.md` | Upstream `gh-upload-log` documentation snapshot | |
| 45 | +| `raw-data/gh-upload-log-cli.js` | Upstream CLI argument handling snapshot | |
| 46 | +| `raw-data/gh-upload-log-index.js` | Upstream upload-strategy and fallback snapshot | |
| 47 | +| `raw-data/gh-upload-log-repository-upload.js` | Upstream shared-vs-dedicated repository logic snapshot | |
| 48 | +| `raw-data/gh-upload-log-pr-28.json` | Related upstream collision-handling PR metadata | |
| 49 | +| `raw-data/gh-upload-log-pr-30.json` | Related upstream shared-repository PR metadata | |
| 50 | +| `raw-data/gh-upload-log-issue-31.json` | Filed upstream fallback-routing issue metadata | |
| 51 | +| `raw-data/gh-upload-log-issue-31-comments.json` | Filed upstream fallback-routing issue comments | |
| 52 | +| `raw-data/gh-upload-log-pr-32.json` | Upstream fallback-routing fix PR metadata | |
| 53 | +| `raw-data/gh-upload-log-npm-metadata.json` | Current npm package metadata for `gh-upload-log` | |
| 54 | +| `logs/tmp-solution-draft-log-pr-1781180521736.txt.gz` | Full 20,632,466 byte linked log, compressed | |
| 55 | +| `logs/tmp-solution-draft-log-pr-1781180537724.txt.gz` | Full 20,653,037 byte linked log, compressed | |
| 56 | +| `logs/gist-upload-evidence-1781180008338.txt` | Earlier successful gist upload from the same run | |
| 57 | +| `logs/dedicated-repo-upload-evidence-1781180521736.txt` | Gist rate-limit failure and dedicated-repository fallback | |
| 58 | +| `logs/linked-repo-summary-1781180521736.json` | Concise first linked repository summary | |
| 59 | +| `logs/linked-repo-summary-1781180537724.json` | Concise second linked repository summary | |
| 60 | +| `logs/run-version-evidence.txt` | Focused run/version/upload command evidence | |
| 61 | +| `research-sources.json` | Online and repository source list | |
| 62 | +| `upstream-gh-upload-log-issue.md` | Body used to file the upstream fallback-routing issue | |
| 63 | + |
| 64 | +The full downloaded logs were verified before compression: |
| 65 | + |
| 66 | +- `tmp-solution-draft-log-pr-1781180521736.txt`: 73,484 lines, 20,632,466 bytes. |
| 67 | +- `tmp-solution-draft-log-pr-1781180537724.txt`: 73,790 lines, 20,653,037 bytes. |
| 68 | + |
| 69 | +## Timeline |
| 70 | + |
| 71 | +| Time (UTC) | Event | |
| 72 | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | |
| 73 | +| 2026-06-11 12:13:29 | Hive Mind uploaded a 9.26 MB solution draft log through `gh-upload-log` automatic mode. The gist upload succeeded. | |
| 74 | +| 2026-06-11 12:13:35 | Hive Mind posted the gist-backed log comment to `lefinepro/kefine#173`. | |
| 75 | +| 2026-06-11 12:13:56 to 12:21:59 | GitHub repeatedly returned secondary content-creation rate limits while the process tried to create comments. | |
| 76 | +| 2026-06-11 12:22:03 | Hive Mind invoked `gh-upload-log` automatic mode for `/tmp/solution-draft-log-pr-1781180521736.txt`, a 19.68 MB file. | |
| 77 | +| 2026-06-11 12:22:05 | Gist creation failed with a GitHub secondary rate limit, and `gh-upload-log` fell back to repository mode. | |
| 78 | +| 2026-06-11 12:22:06 | The first one-off public repository was created: `log-tmp-solution-draft-log-pr-1781180521736.txt`. | |
| 79 | +| 2026-06-11 12:22:21 | The second linked repository initial commit was created. | |
| 80 | +| 2026-06-11 12:22:22 | The second one-off public repository was created: `log-tmp-solution-draft-log-pr-1781180537724.txt`. | |
| 81 | +| 2026-06-11 14:16:43 | Issue #1902 was opened to report the unexpected one-off repositories. | |
| 82 | +| 2026-06-11 22:32:15 | Upstream issue `link-foundation/gh-upload-log#31` was filed with reproduction details and a suggested fix. | |
| 83 | +| 2026-06-12 08:05:39 | Upstream PR `link-foundation/gh-upload-log#32` was merged and issue #31 was closed. | |
| 84 | +| 2026-06-12 08:06:44 | `gh-upload-log` 0.8.1 was published to npm as the latest version. | |
| 85 | + |
| 86 | +## Requirements |
| 87 | + |
| 88 | +1. Default Hive Mind log uploads must not create a separate repository per log. |
| 89 | +2. Hive Mind should keep using `gh-upload-log` auto mode. |
| 90 | +3. Logs that fit the GitHub file limit should attempt gist uploads first. |
| 91 | +4. If gist upload fails and repository fallback is used, the fallback should use |
| 92 | + shared `public-logs` or `private-logs` repositories. |
| 93 | +5. Repository-per-log behavior should only happen when explicitly requested with |
| 94 | + `gh-upload-log` options such as `--no-shared-repository`. |
| 95 | +6. Report the fallback-routing bug upstream with a reproduction, workarounds, and |
| 96 | + a suggested code fix. |
| 97 | +7. The fix needs a regression test that would have failed for the captured path. |
| 98 | +8. Preserve logs, metadata, timeline, root-cause analysis, and solution notes in |
| 99 | + `docs/case-studies/issue-1902/`. |
| 100 | +9. After the upstream fix is available, apply the latest `gh-upload-log` package |
| 101 | + and avoid explicit strategy flags unless Hive Mind needs to override the |
| 102 | + package defaults. |
| 103 | + |
| 104 | +## Root Cause |
| 105 | + |
| 106 | +Hive Mind called `gh-upload-log` in automatic mode: |
| 107 | + |
| 108 | +```text |
| 109 | +gh-upload-log "/tmp/solution-draft-log-pr-1781180521736.txt" --public --description "..." --verbose |
| 110 | +``` |
| 111 | + |
| 112 | +That is the correct high-level integration point: auto mode should select gist |
| 113 | +when possible and repository storage when needed. In the captured run, |
| 114 | +`gh-upload-log` chose gist mode for the 19.68 MB file because it was under the |
| 115 | +25 MB gist limit. When GitHub rejected gist creation with a secondary rate limit, |
| 116 | +upstream automatic mode fell back to repository mode. |
| 117 | + |
| 118 | +The fallback still had `useSharedRepository: true`, but upstream shared-repository |
| 119 | +routing only applies when the file is larger than the gist limit: |
| 120 | + |
| 121 | +```js |
| 122 | +return useSharedRepository && getFileSize(filePath) > GITHUB_GIST_FILE_LIMIT; |
| 123 | +``` |
| 124 | + |
| 125 | +For a 19.68 MB file, that returned `false`, so repository fallback used the legacy |
| 126 | +dedicated repository path and created `log-tmp-solution-draft-log-pr-1781180521736.txt`. |
| 127 | +The second linked repository has the same shape: a single public repository with one |
| 128 | +20.65 MB log file and an initial `Add log file` commit. |
| 129 | + |
| 130 | +The bug was therefore not that auto mode exists, and not that shared repositories |
| 131 | +were unavailable. The bug is that `gh-upload-log` repository fallback only uses |
| 132 | +shared repositories when the original file is larger than the gist limit. Once |
| 133 | +auto mode has already fallen back into repository upload, repository routing |
| 134 | +should depend on `useSharedRepository`, not on the original gist-size decision. |
| 135 | + |
| 136 | +## Solution |
| 137 | + |
| 138 | +Implemented changes: |
| 139 | + |
| 140 | +1. Filed upstream issue |
| 141 | + [link-foundation/gh-upload-log#31](https://github.com/link-foundation/gh-upload-log/issues/31) |
| 142 | + with the reproduction, workarounds, and suggested routing fix. |
| 143 | +2. Confirmed upstream PR |
| 144 | + [link-foundation/gh-upload-log#32](https://github.com/link-foundation/gh-upload-log/pull/32) |
| 145 | + fixed fallback routing by using shared repositories whenever |
| 146 | + `useSharedRepository` is enabled. |
| 147 | +3. Updated Hive Mind Docker images to install `gh-upload-log@latest`, which |
| 148 | + resolves to 0.8.1 at the time of this case study. |
| 149 | +4. Kept Hive Mind uploads in the package's default auto mode by passing only the |
| 150 | + log file, visibility, description, and optional verbose flag. |
| 151 | +5. Added `buildGhUploadLogArgs()` so tests can assert the exact wrapper CLI |
| 152 | + arguments without invoking GitHub. |
| 153 | + |
| 154 | +With this fix, Hive Mind preserves `gh-upload-log` auto behavior without |
| 155 | +duplicating upstream strategy policy. Dedicated one-off repositories remain an |
| 156 | +upstream opt-in via `--no-shared-repository` or `useSharedRepository: false`. |
| 157 | + |
| 158 | +## Regression Coverage |
| 159 | + |
| 160 | +Added `tests/test-issue-1902-log-upload-routing.mjs`. |
| 161 | + |
| 162 | +The test covers: |
| 163 | + |
| 164 | +- Default wrapper arguments rely on `gh-upload-log` defaults for auto mode. |
| 165 | +- Default wrapper arguments do not include `--auto`, `--shared-repository`, |
| 166 | + `--only-gist`, `--only-repository`, or `--no-shared-repository`. |
| 167 | +- Public and private wrapper arguments still set visibility and preserve the log |
| 168 | + description. |
| 169 | + |
| 170 | +Focused verification: |
| 171 | + |
| 172 | +```bash |
| 173 | +node tests/test-issue-1902-log-upload-routing.mjs |
| 174 | +``` |
| 175 | + |
| 176 | +## Online Research |
| 177 | + |
| 178 | +GitHub documents secondary REST API limits and recommends pausing when a secondary |
| 179 | +limit is returned. The captured failure is consistent with those docs: GitHub |
| 180 | +returned HTTP 403 secondary-rate-limit responses for content creation around the |
| 181 | +same time as the gist upload failed. |
| 182 | + |
| 183 | +GitHub's file attachment and repository large-file documentation confirm the 25 MB |
| 184 | +boundary used by Hive Mind's `githubLimits.fileMaxSize`. The upstream `gh-upload-log` |
| 185 | +README and implementation confirm that gist is the intended path under that limit, |
| 186 | +and shared repositories are the intended path for repository-mode logs. The 0.8.1 |
| 187 | +package metadata confirms the fallback-routing fix was published after upstream |
| 188 | +PR #32 merged. |
| 189 | + |
| 190 | +Sources are listed in `research-sources.json`. |
| 191 | + |
| 192 | +## External Issues |
| 193 | + |
| 194 | +Filed upstream issue |
| 195 | +[link-foundation/gh-upload-log#31](https://github.com/link-foundation/gh-upload-log/issues/31). |
| 196 | +The issue includes the captured reproduction, the `shouldUseSharedRepositoryMode()` |
| 197 | +root cause, caller workarounds, and a suggested code-level fix: repository routing |
| 198 | +should use shared repositories whenever `useSharedRepository` is true, including |
| 199 | +auto-mode fallback after a gist failure below the gist limit. |
| 200 | + |
| 201 | +The issue is now closed by |
| 202 | +[link-foundation/gh-upload-log#32](https://github.com/link-foundation/gh-upload-log/pull/32), |
| 203 | +and the fix is published in `gh-upload-log` 0.8.1. |
0 commit comments