From f8bf9ca0daab1d1baa19df26185c092521ae9c7a Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Tue, 24 Mar 2026 21:02:39 +0100 Subject: [PATCH 1/3] fix(home): handle GitHub API failures gracefully in releases/changelogs (#3291) Wrap GitHub API calls in try/catch and return empty array on failure. Prevents 422 errors when rate-limited or repos are unreachable. --- modules/home/services/home.service.js | 62 +++++++++++++++------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/modules/home/services/home.service.js b/modules/home/services/home.service.js index 52cb55341..0a2d7bed8 100644 --- a/modules/home/services/home.service.js +++ b/modules/home/services/home.service.js @@ -32,21 +32,25 @@ const page = async (name) => { * @return {Promise} All versions */ const releases = async () => { - const requests = config.repos.map((item) => - axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/releases`, { - headers: item.token ? { Authorization: `token ${item.token}` } : {}, - }), - ); - let results = await axios.all(requests); - results = results.map((result, i) => ({ - title: config.repos[i].title, - list: result.data.map((release) => ({ - name: release.name, - prerelease: release.prerelease, - published_at: release.published_at, - })), - })); - return Promise.resolve(results); + try { + const requests = config.repos.map((item) => + axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/releases`, { + headers: item.token ? { Authorization: `token ${item.token}` } : {}, + }), + ); + let results = await axios.all(requests); + results = results.map((result, i) => ({ + title: config.repos[i].title, + list: result.data.map((release) => ({ + name: release.name, + prerelease: release.prerelease, + published_at: release.published_at, + })), + })); + return results; + } catch (_err) { + return []; + } }; /** @@ -54,18 +58,22 @@ const releases = async () => { * @return {Promise} All changelogs */ const changelogs = async () => { - const repos = _.filter(config.repos, (repo) => repo.changelog); - const requests = repos.map((item) => - axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/contents/${item.changelog}`, { - headers: item.token ? { Authorization: `token ${item.token}` } : {}, - }), - ); - let results = await axios.all(requests); - results = results.map((result, i) => ({ - title: config.repos[i].title, - markdown: Base64.decode(result.data.content), - })); - return Promise.resolve(results); + try { + const repos = _.filter(config.repos, (repo) => repo.changelog); + const requests = repos.map((item) => + axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/contents/${item.changelog}`, { + headers: item.token ? { Authorization: `token ${item.token}` } : {}, + }), + ); + let results = await axios.all(requests); + results = results.map((result, i) => ({ + title: repos[i].title, + markdown: Base64.decode(result.data.content), + })); + return results; + } catch (_err) { + return []; + } }; /** From 2803e5ff33276a750e3bc400eb27a871f7c244f8 Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Tue, 24 Mar 2026 21:10:01 +0100 Subject: [PATCH 2/3] fix(home): align error tests with graceful GitHub API fallback Tests now expect 200 with empty data when GitHub API fails, matching the service's catch-and-return-empty-array behavior. --- modules/home/tests/home.integration.tests.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/home/tests/home.integration.tests.js b/modules/home/tests/home.integration.tests.js index f2b2fdd46..f438dfff3 100644 --- a/modules/home/tests/home.integration.tests.js +++ b/modules/home/tests/home.integration.tests.js @@ -91,20 +91,20 @@ describe('Home integration tests:', () => { } }); - test('should return 422 when GitHub API fails for releases', async () => { + test('should return empty releases gracefully when GitHub API fails', async () => { axios.get.mockRejectedValueOnce(new Error('GitHub API unavailable')); - const result = await agent.get('/api/home/releases').expect(422); - expect(result.body.type).toBe('error'); - expect(result.body.message).toBe('Unprocessable Entity'); - expect(result.body.description).toBe('GitHub API unavailable.'); + const result = await agent.get('/api/home/releases').expect(200); + expect(result.body.type).toBe('success'); + expect(result.body.message).toBe('releases'); + expect(result.body.data).toEqual([]); }); - test('should return 422 when GitHub API fails for changelogs', async () => { + test('should return empty changelogs gracefully when GitHub API fails', async () => { axios.get.mockRejectedValueOnce(new Error('GitHub API unavailable')); - const result = await agent.get('/api/home/changelogs').expect(422); - expect(result.body.type).toBe('error'); - expect(result.body.message).toBe('Unprocessable Entity'); - expect(result.body.description).toBe('GitHub API unavailable.'); + const result = await agent.get('/api/home/changelogs').expect(200); + expect(result.body.type).toBe('success'); + expect(result.body.message).toBe('changelogs'); + expect(result.body.data).toEqual([]); }); test('should use Authorization header when a token is configured for releases', async () => { From 009023ba50c37727cd57e29ac2f942502e3bf45c Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Tue, 24 Mar 2026 21:11:21 +0100 Subject: [PATCH 3/3] =?UTF-8?q?docs(claude):=20add=20workflow=20rules=20?= =?UTF-8?q?=E2=80=94=20PRs=20required,=20thresholds,=20module=20audit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 07743a821..d7a394bd6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,6 +67,13 @@ Node / Express / Mongoose / JWT stack from Devkit. Standalone backend or fullsta - PRs: always use `/pull-request` — never open manually - After user correction, evaluate if the pattern belongs in `ERRORS.md` +## Workflow rules + +- **Never push directly to master/main.** Always create a branch, push, create a PR, wait for CI green + review, then merge. +- **Never lower coverage thresholds** in `jest.config.js`. If coverage drops after adding code, write tests for the new project modules to bring it back above thresholds. +- **Audit existing modules before implementing.** Before creating new storage, file handling, or utility code, check `modules/` for existing solutions (e.g., `uploads` module for file storage via GridFS). +- **Always run `/verify` after any code change** before declaring done. CI must be green. + ## Skills | Skill | Description |